A production-ready ASP.NET Core 8.0 Web API for a stock ticker service. Designed with microservice best practices — clean layering, interface-driven DI, Auth0 JWT security, structured logging, distributed tracing, and CI/CD via GitHub Actions to Azure.
- Tech Stack
- Architecture
- Design Patterns
- Project Structure
- Middleware Pipeline
- Authentication & Authorization
- Configuration
- Observability
- API Documentation
- Getting Started
- Docker
- CI/CD
- Roadmap
| Concern | Technology |
|---|---|
| Framework | ASP.NET Core 8.0 |
| Authentication | Auth0 (JWT Bearer via OIDC) |
| Logging | Serilog (structured, configurable sinks) |
| API Docs | Swagger / OpenAPI 3 |
| Observability | Correlation ID tracing, New Relic |
| Caching | ASP.NET Response Caching |
| Containerization | Docker (multi-stage), Docker Compose |
| CI/CD | GitHub Actions → Azure Web App |
This service follows a layered architecture appropriate for a microservice: thin controllers delegate to a service layer, which contains all business logic. Repositories (not yet added) would sit below services and abstract data access.
┌──────────────────────────────────────────────┐
│ HTTP Clients │
└────────────────────┬─────────────────────────┘
│
┌────────────▼────────────┐
│ Middleware Pipeline │ ← Correlation ID, Logging, Exception Handler
└────────────┬────────────┘
│
┌────────────▼────────────┐
│ Controllers │ ← Route handling, Auth policies, HTTP semantics
└────────────┬────────────┘
│
┌────────────▼────────────┐
│ Service Layer │ ← Business logic, interface-driven
└────────────┬────────────┘
│
┌────────────▼────────────┐
│ Repository Layer │ ← (planned) Data access abstraction
└────────────┬────────────┘
│
┌────────────▼────────────┐
│ External / Data │ ← Database, external stock APIs
└─────────────────────────┘
- Single Responsibility — each layer has one job; controllers don't contain business logic
- Dependency Inversion — all dependencies are injected via interfaces, not concrete types
- Open/Closed — new features extend the service layer without modifying controllers
- Fail Fast — required configuration (
AppSettings) throws at startup if missing, preventing silent misconfigurations
All services are registered against interfaces. This decouples consumers from implementations and enables unit testing via mock substitution.
// Registration (Program.cs)
builder.Services.AddSingleton<IWeatherForecastService, WeatherForecastService>();
// Consumption (Controller)
public WeatherForecastController(IWeatherForecastService forecastService) { ... }Settings are bound from appsettings.json to a typed AppSettings root object at startup. Nested sub-configurations (Auth0Configuration, CacheSettings, etc.) are passed directly to extension methods — no raw IConfiguration leaking into services.
var appSettings = configuration.Get<AppSettings>()
?? throw new InvalidOperationException("AppSettings configuration is missing.");
builder.Services.AddSingleton(appSettings);Cross-cutting concerns (Auth0, Swagger) are organized as extension methods in their own namespaces. This keeps Program.cs clean and each concern self-contained and independently testable.
builder.Services.AddAuth0Authentication(appSettings.Auth0);
builder.Services.AddAuth0Authorization(appSettings.Auth0);ExceptionHandlerMiddleware catches all unhandled exceptions, logs them with Serilog, and returns a consistent 500 JSON response. Consumers always receive a structured error — never a stack trace or HTML error page.
{ "error": "An error occurred while processing your request." }CorrelationIdHeaderMiddleware ensures every request carries a correlationId header. If the caller provides one (upstream service), it is preserved. If not, a new GUID is generated. The ID is echoed back in the response header, enabling end-to-end trace correlation across microservices.
femb-stock-ticker/
├── FembStockTicker/
│ ├── Auth0/ # Auth0 extension methods (authn + authz setup)
│ ├── Config/ # Strongly-typed settings classes
│ │ ├── AppSettings.cs # Root configuration object
│ │ ├── AppConfiguration.cs # API version, identifier, host, environment
│ │ ├── ApiDirectory.cs # External API directory / downstream services
│ │ ├── Auth0Configuration.cs # Auth0 domain, audience, scopes
│ │ ├── CacheSettings.cs # Response cache duration settings
│ │ ├── CacheApplicationConfiguration.cs
│ │ ├── HealthCheckSettings.cs # Health probe configuration
│ │ ├── LoggingSettings.cs # Logging level and sink configuration
│ │ ├── Splunk.cs # Splunk sink configuration (planned)
│ │ └── Trace.cs # Distributed tracing configuration
│ ├── Controllers/ # API endpoints — thin, delegate to services
│ ├── Middleware/
│ │ ├── CorrelationIdHeaderMiddleware.cs
│ │ └── ExceptionHandlerMiddleware.cs
│ ├── Models/ # Data transfer objects and domain models
│ ├── Services/ # Business logic behind interfaces
│ │ ├── IWeatherForecastService.cs
│ │ └── WeatherForecastService.cs
│ ├── Swagger/ # OpenAPI configuration and custom attributes
│ │ ├── Attributes/
│ │ │ └── ConsumesHeaderAttribute.cs # Documents required request headers
│ │ ├── SwaggerConfiguration.cs
│ │ ├── SwaggerConfigurationSettings.cs
│ │ └── SwaggerHeaderAttribute.cs
│ ├── Program.cs # Composition root — DI wiring and middleware pipeline
│ ├── FembStockTicker.http # HTTP request file for manual API testing
│ ├── appsettings.json
│ └── appsettings.Development.json
├── .github/workflows/ # GitHub Actions CI/CD
│ ├── master_femb-stock-ticker.yml # Deploy to Azure Web App on push to master
│ ├── claude.yml # @claude mention handler for issues/PRs
│ ├── claude-code-review.yml # Automated PR code review via Claude
│ └── claude-code-pr-autodoc.yml # Auto-generate PR docs on merge to master
├── Dockerfile # Multi-stage build (sdk:8.0 → aspnet:8.0 runtime)
├── docker-compose.yml # Compose config — reads Auth0 secrets from .env
├── .env.example # Template for local Docker secrets (copy to .env)
├── .dockerignore
├── test-api.sh # Shell script for manual API endpoint testing
└── femb-stock-ticker.sln
Order matters in ASP.NET Core middleware. The pipeline is deliberately ordered for correctness and observability:
Request →
1. CorrelationIdHeaderMiddleware — inject/propagate correlation ID
2. Serilog Request Logging — structured HTTP access logs with correlation context
3. ExceptionHandlerMiddleware — catch-all for unhandled exceptions
4. HTTPS Redirection — enforce TLS
5. Authentication — validate JWT (Auth0)
6. Authorization — enforce scope-based policies
7. Response Caching — cache eligible GET responses
8. Swagger UI (Development only) — interactive API explorer
9. Controllers — route to endpoint handlers
← Response
Placing the exception handler after logging ensures that even error responses are logged with the correlation ID. Auth runs before controllers so protected routes are never reached unauthenticated.
Auth is handled by Auth0 using OpenID Connect with JWT Bearer tokens.
JWT tokens are validated against:
Issuer— Auth0 authority URLAudience— API identifier registered in Auth0Lifetime— token expiry enforcedNameClaimType— mapped toClaimTypes.NameIdentifier
Scope-based policies enforce fine-grained access control:
| Policy | Required Scope | Intended Use |
|---|---|---|
read:stocks |
read:stocks |
GET endpoints (read data) |
write:stocks |
write:stocks |
POST/PUT endpoints |
Apply policies on controllers or actions:
[Authorize(Policy = "read:stocks")]
[HttpGet]
public ActionResult<Stock[]> GetStocks() { ... }Set these in appsettings.Development.json or as environment variables / Azure App Service settings:
{
"Auth0": {
"Domain": "your-tenant.auth0.com",
"Audience": "https://your-api-identifier",
"ClientId": "...",
"ClientSecret": "..."
}
}All settings are strongly typed under AppSettings:
| Section | Purpose |
|---|---|
AppConfiguration |
API version, identifier, host, environment |
Auth0 |
Auth0 domain, audience, scopes |
Cache |
Response cache duration settings |
HealthCheck |
Health probe configuration |
Api |
External API directory / downstream services |
NEW_RELIC_* |
New Relic APM agent configuration |
Sensitive values (Auth0.ClientSecret, NEW_RELIC_LICENSE_KEY) should never be committed to source. Use:
dotnet user-secretsfor local development- Azure Key Vault / App Service configuration for deployed environments
Serilog is configured via appsettings.json with environment-specific overrides. All log entries are structured (not plain text), making them queryable in log aggregation tools.
"Serilog": {
"MinimumLevel": { "Default": "Information" },
"WriteTo": [{ "Name": "Console" }]
}Planned sinks: Splunk (Config/Splunk.cs), file, Azure Application Insights.
Every request carries a correlationId header. In a microservice mesh, pass this header to downstream service calls so the full request trace can be reconstructed across service boundaries.
NEW_RELIC_APP_NAME and NEW_RELIC_LICENSE_KEY in AppSettings wire up New Relic agent-based APM for performance monitoring and alerting in production.
Swagger UI is available in Development mode at:
http://localhost:7236/swagger
The Swagger definition includes:
- JWT Bearer security scheme (paste a token to authorize all requests)
- Per-endpoint auth requirements
- Custom
ConsumesHeaderattribute for documenting required request headers (e.g.,correlationId)
- .NET 8 SDK
- An Auth0 tenant with an API registered (for authenticated endpoints)
- Docker (optional, for containerized runs)
# Restore and build
dotnet build --configuration Release
# Set Auth0 secrets (one-time)
dotnet user-secrets set "Auth0:Domain" "your-tenant.auth0.com" --project FembStockTicker
dotnet user-secrets set "Auth0:Audience" "https://your-api-identifier" --project FembStockTicker
dotnet user-secrets set "Auth0:ClientId" "your clientId" --project FembStockTicker
dotnet user-secrets set "Auth0:ClientSecret" "your clientSecret" --project FembStockTicker
# Run — API at http://localhost:7236, Swagger at http://localhost:7236/swagger
dotnet run --project FembStockTicker# Copy and fill in Auth0 credentials
cp .env.example .env
# Start with Docker Compose — API at http://localhost:8080, Swagger at http://localhost:8080/swagger
docker compose up --build
# Or build and run manually
docker build -t femb-stock-ticker .
docker run -p 8080:8080 \
-e Auth0__Domain=your-domain \
-e Auth0__Audience=your-audience \
-e Auth0__ClientId=your-client-id \
-e Auth0__ClientSecret=your-client-secret \
femb-stock-tickerdotnet publish -c Release -o ./publishGitHub Actions workflows in .github/workflows/:
| Workflow | Trigger | Purpose |
|---|---|---|
master_femb-stock-ticker.yml |
Push to master |
Deploy to Azure Web App via OIDC federated identity |
claude.yml |
@claude mention in issues/PRs |
Interactive Claude Code assistance |
claude-code-review.yml |
Pull request opened | Automated code review via Claude |
claude-code-pr-autodoc.yml |
PR merged to master |
Auto-generate PR documentation |
Recommended secrets to configure in GitHub Actions:
AUTH0_DOMAINAUTH0_AUDIENCENEW_RELIC_LICENSE_KEY
- Replace placeholder
WeatherForecastwithStockdomain (quotes, ticker search, historical data) - Add Repository layer with EF Core or Dapper
- Add Health Check endpoints (
/health,/health/ready) - Add rate limiting (ASP.NET Core built-in rate limiter)
- Add integration tests with
WebApplicationFactory<Program> - Update CI/CD pipeline to target .NET 8
- Add OpenTelemetry traces alongside Serilog logs
- Configure Splunk log forwarding via Serilog sink