The Web building block configures the ASP.NET Core API pipeline for BitzOrcas. It lives in the API Host’s composition root (BitzOrcas.Api.Composition) and follows a 4-stage registration pattern.
4-stage DI registration
// Stage 1: Core runtime (no DB dependency)builder.Services.AddBitzOrcasCoreRuntime(configuration);
// Stage 2: Persistence adapters (requires ConnectionStrings + RabbitMq)builder.Services.AddBitzOrcasPersistenceAdapters(configuration);
// Stage 3: Authentication (JWT / HMAC / API Key)builder.Services.AddBitzOrcasAuthentication(configuration, environment);
// Stage 4: API pipeline (Mediator + JSON + OpenAPI + RateLimiter)builder.Services.AddBitzOrcasApiPipeline(configuration);Middleware pipeline order
ExceptionHandler → CorrelationId → Authentication → DelegationToken→ TenantResolution → RequestAudit → Authorization → RateLimiter → Endpoints| Middleware | Purpose |
|---|---|
ExceptionHandler | Global RFC 9457 ProblemDetails handler (outermost) |
CorrelationIdMiddleware | Generates/propagates X-Correlation-Id header |
Authentication | ASP.NET Core built-in (JWT / HMAC / API Key) |
DelegationTokenMiddleware | Validates delegation (impersonation) tokens |
TenantResolutionMiddleware | Resolves ITenantContext from request |
RequestAuditMiddleware | Enqueues audit entries for background processing |
Authorization | ASP.NET Core policy-based authorization |
RateLimiter | Tenant + caller-partitioned rate limiting |
Mediator integration
BitzOrcas uses Mediator with source-generation — no reflection at runtime:
services.AddMediator(options =>{ options.Assemblies = [typeof(PingQuery).Assembly]; options.ServiceLifetime = ServiceLifetime.Scoped; options.PipelineBehaviors = [ typeof(LoggingPipelineBehavior<,>), typeof(AuthorizationPipelineBehavior<,>), typeof(ValidationPipelineBehavior<,>), typeof(IdempotencyPipelineBehavior<,>), typeof(TransactionPipelineBehavior<,>), typeof(ActivityAuditPipelineBehavior<,>), ];});Mapster object mapping
Object-to-object mapping via Mapster (reflection-based; planned migration to Mapperly for AOT compatibility per ADR 0006):
// Scan all TypeAdapter configurations from Application assemblyTypeAdapterConfig.GlobalSettings.Scan(typeof(MappingConfig).Assembly);System.Text.Json (AOT-safe)
All API serialization uses source-generated JSON contexts — compatible with Native AOT trimming:
services.ConfigureHttpJsonOptions(options =>{ options.SerializerOptions.TypeInfoResolverChain.Insert(0, ApiJsonSerializerContext.Default);});OpenAPI + Scalar UI
BitzOrcas uses the built-in .NET OpenAPI support with Scalar as the API documentation UI:
services.AddOpenApi(); // .NET built-in// Scalar UI served at /scalarRate limiting
Three partitioned rate-limiting policies with per-tenant + per-caller keys:
| Policy | Algorithm | Use case |
|---|---|---|
userPolicy | Token Bucket | General REST API endpoints |
sensitivePolicy | Sliding Window | Login, OTP, password reset |
fixedPolicy | Fixed Window | Internal/low-frequency services |
Partition key format: {tenant_id}:{user_id|client_id} — prevents application callers (without user_id) from sharing the same anonymous bucket.
ProblemDetails error handling
All exceptions are converted to RFC 9457 ProblemDetails by GlobalExceptionHandler:
{ "type": "https://docs.bitzsoft.com/problems/not-found", "title": "Not Found", "status": 404, "detail": "Note with ID 'abc' not found.", "instance": "/api/notes/abc", "traceId": "00-abc123-...", "correlationId": "corr-456", "requestId": "req-789", "errorCode": "Note.NotFound"}Result<T> to HTTP translation
ResultExtensions provides extension methods for converting Result<T> to HTTP responses in Minimal API endpoints:
app.MapPost("/api/notes", async ( CreateNoteCommand cmd, IMediator mediator) => await mediator.Send(cmd) switch { { IsSuccess: true } result => Results.Created( $"/api/notes/{result.Value.Id}", result.Value), var result => result.ToProblemDetails() });