US State Explorer – Technical Details

Back to Demo

High-Level System Overview

  • Purpose: Explore ArcGIS state feature data, save selected map features, attach notes, and organize saved features into collections.
  • Business problem: Demonstrates how a geospatial browsing experience can be connected to user-specific persistence without requiring sign-in for basic saved-feature workflows.
  • Architecture: Razor Page + ArcGIS JavaScript SDK → versioned ASP.NET Core APIs → scoped services → repositories → PostgreSQL via EF Core, with an ArcGIS proxy service for feature queries.
  • Technologies: .NET 10, Razor Pages, ArcGIS JavaScript SDK, Bootstrap, Typeahead.js, EF Core/Npgsql, Google OAuth, anonymous identity middleware.
  • Deployment model: Runs in the Render-hosted Portfolio app with Docker and Kubernetes manifests available for containerized cloud deployment.

Frontend Architecture

The front end is a Razor Page that delegates rich map behavior to focused JavaScript modules under wwwroot/js/StateExplorer. This keeps the page server-rendered while allowing map, search, saved-feature, and collection interactions to evolve independently.

  • Module boundaries: mapManager.js owns ArcGIS map lifecycle; featureService.js and collectionService.js wrap API calls; stateStore.js tracks client state; uiManager.js updates the DOM.
  • API communication: Shared api-config.js constants provide versioned routes for features, saved features, and collections.
  • State management: Client-side state tracks active layer, selected feature, saved features, collections, map highlights, and theme preferences without a SPA framework.
  • Authentication handling: Saved features use the anonymous cookie automatically sent with API calls; collections use the shared fetch helper to redirect unauthenticated users to Google login.
  • Performance: ArcGIS handles map rendering in the browser, while the backend handles persistence, ownership, validation, and protected proxy calls.

API Layer

  • Feature query API: FeaturesController exposes GET /api/v1/features?layerId={layerId}&bbox={bbox} as an anonymous ArcGIS feature proxy with circuit-breaker protection.
  • Saved feature API: SavedFeaturesController exposes /api/v1/features/saved for anonymous per-user saved-feature CRUD.
  • Collections API: CollectionsController exposes authenticated collection CRUD under /api/v1/collections.
  • Identity model: Anonymous saved-feature ownership comes from AnonUserId in HttpContext.Items; authenticated collections use the Authenticated policy.
  • Validation and errors: Controllers validate trivial request shape, translate service exceptions to 400/404/409, and return DTOs rather than EF entities.
  • Security: The ArcGIS proxy URL-encodes caller-supplied query parts and prevents direct persistence based on client-supplied owner ids.

Services and Business Logic

  • ArcGIS service: ArcGisService builds outbound ArcGIS requests, deserializes feature data, and uses typed HttpClient resilience policies.
  • Saved features: SavedFeatureService resolves the current anonymous user, enforces duplicate prevention, assigns timestamps, and wraps multi-step writes in an EF Core transaction.
  • Collections: CollectionService resolves authenticated user ownership, trims input, enforces unique collection names, and applies default colors.
  • Service contracts: Services return DTOs and use typed exceptions such as ArgumentException, InvalidOperationException, and KeyNotFoundException for controller translation.
  • Geospatial workflow: Browser map interactions identify features; backend services decide which selected feature data can be persisted and how it is scoped to the visitor.

Data Access Layer

  • Repository pattern: SavedFeatureRepository and CollectionRepository own EF Core queries, eager loading, owner filtering, and SaveChangesAsync.
  • Database: PostgreSQL is accessed with EF Core/Npgsql, fluent mappings, and startup migrations.
  • Ownership filtering: Per-user queries filter by anonymous UserId or authenticated OwnerId at the repository layer.
  • Query shape: Saved-feature reads eagerly load Collection and UserNotes so DTO mapping can include collection names and note data without lazy loading.
  • Transaction handling: Multi-step saved-feature creation uses BeginTransactionAsync, CommitAsync, and RollbackAsync to preserve atomicity.

Infrastructure and Deployment

  • Hosting: Render hosts the .NET app; PostgreSQL, Google OAuth settings, and optional Redis settings are supplied by configuration.
  • Middleware: ApiExceptionMiddleware, AnonymousUserMiddleware, authentication, and authorization run in a defined order so anonymous and authenticated identities coexist.
  • Redis integration: Redis is used by the wider application for geocoding cache, batch geocoding job state, and production Data Protection key storage when Redis__ConnectionString is configured.
  • Data protection: Local/container deployments can persist keys to the filesystem; Redis-backed key persistence stores keys under DataProtection-Keys and supports multi-replica cookie validity when Redis is configured.
  • Containerization: Docker packages the application; Kubernetes manifests model replica scaling, config/secret injection, and service exposure.
  • Cloud portability: The same boundaries map to Azure App Service or container platforms with managed PostgreSQL, Azure Cache for Redis, and externalized OAuth secrets.

Engineering Decisions and Tradeoffs

  • Anonymous identity: Lowers friction for saved features, but requires careful separation from authenticated collection ownership.
  • Server proxy for features: Centralizes URL encoding, resilience, and API contract control, but adds backend latency compared with direct browser-only feature queries.
  • Layered architecture: Keeps controllers thin and services testable, at the cost of more files and interfaces.
  • Future improvements: Add per-user quotas, ArcGIS response caching, collection sharing, richer spatial filtering, OpenTelemetry traces, and managed cloud secrets/key storage.

Interview Discussion Points

  • How do anonymous and authenticated identities coexist without leaking data across users?
  • What should be cached: ArcGIS feature responses, saved-feature DTOs, or both?
  • How would you protect the ArcGIS proxy from abusive bbox/layer queries?
  • Where should geospatial filtering run: browser, API proxy, database, or upstream ArcGIS?
  • How would you evolve saved features into a multi-user collaboration feature?