US State Explorer – Technical Details
Back to DemoHigh-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.jsowns ArcGIS map lifecycle;featureService.jsandcollectionService.jswrap API calls;stateStore.jstracks client state;uiManager.jsupdates the DOM. - API communication: Shared
api-config.jsconstants 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:
FeaturesControllerexposesGET /api/v1/features?layerId={layerId}&bbox={bbox}as an anonymous ArcGIS feature proxy with circuit-breaker protection. - Saved feature API:
SavedFeaturesControllerexposes/api/v1/features/savedfor anonymous per-user saved-feature CRUD. - Collections API:
CollectionsControllerexposes authenticated collection CRUD under/api/v1/collections. - Identity model: Anonymous saved-feature ownership comes from
AnonUserIdinHttpContext.Items; authenticated collections use theAuthenticatedpolicy. - 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:
ArcGisServicebuilds outbound ArcGIS requests, deserializes feature data, and uses typedHttpClientresilience policies. - Saved features:
SavedFeatureServiceresolves the current anonymous user, enforces duplicate prevention, assigns timestamps, and wraps multi-step writes in an EF Core transaction. - Collections:
CollectionServiceresolves 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, andKeyNotFoundExceptionfor 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:
SavedFeatureRepositoryandCollectionRepositoryown EF Core queries, eager loading, owner filtering, andSaveChangesAsync. - Database: PostgreSQL is accessed with EF Core/Npgsql, fluent mappings, and startup migrations.
- Ownership filtering: Per-user queries filter by anonymous
UserIdor authenticatedOwnerIdat the repository layer. - Query shape: Saved-feature reads eagerly load
CollectionandUserNotesso DTO mapping can include collection names and note data without lazy loading. - Transaction handling: Multi-step saved-feature creation uses
BeginTransactionAsync,CommitAsync, andRollbackAsyncto 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__ConnectionStringis configured. - Data protection: Local/container deployments can persist keys to the filesystem; Redis-backed key persistence stores keys under
DataProtection-Keysand 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?