UrlPulse is a scalable, cloud-native website monitoring platform built with ASP.NET Core 10. It tracks website latency, uptime, and 2xx status code reliability across multiple geographic regions, providing users with actionable analytics over time.
Beyond its core functionality, UrlPulse was built as an iterative exercise in modern software engineering, focusing on DevOps, cost optimization, distributed systems, and enterprise-grade security.
- Multi-Region Monitoring: Background workers deployed across 5 North American regions to measure latency and pinpoint geographical performance issues.
- Latency Analytics: A dashboard for users to visualize latency trends, averages, percentile values, and reliability breakdowns over time.
- Enterprise-Grade Multi-Tenancy: Secure authentication via Microsoft Entra ID (Azure AD), strictly binding records to immutable Object IDs (OIDs) to guarantee data isolation.
- Cost-Optimized Architecture: A decoupled, serverless backend that scales to zero during idle periods, minimizing cloud overhead.
- Framework: .NET 10, C#, ASP.NET Core Razor Pages
- Database: PostgreSQL, Entity Framework (EF) Core
- Cloud & Hosting (Azure): Azure Container Apps, Azure Functions (Serverless), Azure Key Vault, Application Insights
- Identity: Microsoft Entra ID (Azure AD)
- DevOps: Docker, GitHub Actions (CI/CD)
- Testing: xUnit, Moq, Fluent Assertions
The initial minimum viable product was a containerized ASP.NET Core Razor app backed by PostgreSQL. The primary focus was establishing a professional development lifecycle: robust unit testing, safe database migrations, and an automated CI/CD pipeline utilizing Docker and GitHub Actions.
Reviewing Azure Cost Analysis revealed that keeping a container replica active 24/7 for background polling was highly inefficient.
- The Fix: Background checking logic was decoupled and moved into Azure Functions. This allowed the main app to scale down while the serverless functions executed on a schedule.
- Precision: To combat serverless "cold starts" and timer drift, a tolerance value was engineered into the checking interval to guarantee no monitoring windows were missed.
- Centralized Configuration: Azure Key Vault was integrated to serve connection strings securely to both the Container App and the Function Apps.
To turn the platform into a multi-tenant SaaS application, the architecture required strict data isolation.
- Global Query Filters: Tenant data isolation is enforced at the database level. EF Core global query filters automatically inject the authenticated user's OID into every query, making cross-tenant data leakage virtually impossible.
- Telemetry & Debugging: Azure Application Insights is heavily utilized for distributed tracing and identifying configuration errors without guesswork.
Software engineering is an ongoing process of optimization. The following architectural improvements are planned to manage technical debt and prepare the system for higher usage:
- Asynchronous Polling: Refactor the Azure Function worker to utilize
Task.WhenAllfor concurrent HTTP polling. Because the EF CoreDbContextis not thread-safe, database writes will be executed sequentially after the concurrent network calls complete. - Cancellation Token Propagation: Pass
CancellationTokens completely through the stack (UI -> Controllers -> Services) and combine them with timeout tokens. This ensures orphaned operations (e.g., a user closing their browser, or an app instance shutting down) do not consume unnecessary compute.
- CI/CD Database Migrations: Move automatic database migrations out of the application startup pipeline (
Program.cs). In a distributed system, multiple app instances spinning up simultaneously can cause race conditions. Migrations will be handled exclusively by the CI/CD pipeline. - Reverse Proxy Headers: Evaluate the necessity of Forwarded Headers depending on the final load balancer/ingress configuration.
- Encapsulation: Remove public setters from domain models. Implement explicit methods (e.g.,
SetPause()) to mutate state, ensuring business logic rules are centralized within the model. - Fail-Fast Mechanics: Enforce strict data integrity in
ApplicationDbContext.SaveChanges(). Instead of savingstring.Emptyfor a missingUserId, the system will throw anInvalidOperationException. - Data Types: Migrate
DateTimefields toDateTimeOffsetto better handle timezone discrepancies across globally distributed Azure Functions. - Error Logging: Expand the
UrlCheckResultrecord to include optional error message properties to surface detailed HTTP failure reasons.