The middleware pipeline is not strictly linear — Map, MapWhen, and UseWhen allow the pipeline to branch based on request path or conditions. This enables different middleware stacks for different areas of the application: admin endpoints with additional auth middleware, webhook endpoints that bypass auth and add signature validation, health check endpoints that bypass all middleware overhead, or versioned API pipelines with different behaviour per version prefix.
Map — Path-Based Branching
// ── Map — branch on path prefix ───────────────────────────────────────────
// The branch runs INSTEAD OF the main pipeline for matching paths
app.Map("/api/v1", v1App =>
{
v1App.UseMiddleware<V1DeprecationHeaderMiddleware>();
v1App.UseRouting();
v1App.UseAuthentication();
v1App.UseAuthorization();
v1App.MapControllers();
});
app.Map("/api/v2", v2App =>
{
// v2 gets a different pipeline (no deprecation header)
v2App.UseRouting();
v2App.UseAuthentication();
v2App.UseAuthorization();
v2App.MapControllers();
});
// Map for webhooks — bypass JWT auth, add signature validation instead
app.Map("/webhooks", webhooksApp =>
{
webhooksApp.UseMiddleware<WebhookSignatureValidationMiddleware>();
webhooksApp.MapControllers();
});
Map creates a completely separate sub-pipeline — the main pipeline’s middleware does NOT run for requests that match the branch. This is the key difference between Map and UseWhen: Map branches off and the main pipeline never executes for those requests; UseWhen runs the conditional middleware and then rejoins the main pipeline. Use Map when you want a completely different pipeline; use UseWhen when you want to add optional middleware to the existing pipeline.Map("/health") to create a dedicated health check sub-pipeline that bypasses authentication, CORS, compression, and other middleware that adds overhead. Health check endpoints are called frequently by load balancers and Kubernetes probes — keeping their pipeline minimal reduces their latency and server load. Similarly, Prometheus metrics endpoints (/metrics) should bypass auth for scraping from within the cluster network.MapWhen branches the pipeline based on a predicate and does NOT rejoin the main pipeline. If you need the middleware to execute conditionally but then continue through the main pipeline, use UseWhen instead. A common mistake is using MapWhen expecting the request to continue to controllers after the conditional middleware — it does not, because MapWhen creates a dead-end branch without further endpoint mapping.UseWhen — Conditional Middleware Without Branching
// ── UseWhen — apply middleware conditionally; rejoins main pipeline ────────
// Use this when you want to add middleware ONLY for some requests
// but then CONTINUE through the rest of the pipeline
// Apply API key validation only to /api/* paths
app.UseWhen(
context => context.Request.Path.StartsWithSegments("/api"),
apiApp =>
{
apiApp.UseMiddleware<ApiKeyValidationMiddleware>();
});
// The main pipeline continues after UseWhen for ALL requests
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
// ── MapWhen — conditional branch that does NOT rejoin ─────────────────────
// Use when requests should be handled completely differently
app.MapWhen(
context => context.Request.Headers.ContainsKey("X-Webhook-Signature"),
webhookApp =>
{
webhookApp.UseMiddleware<WebhookMiddleware>();
webhookApp.Run(async ctx => await ctx.Response.WriteAsync("Webhook received."));
// Requests matched here NEVER reach MapControllers
});
Common Mistakes
Mistake 1 — Using MapWhen expecting requests to continue to controllers
❌ Wrong — MapWhen creates a branch that never reaches the main pipeline’s endpoint mapping.
✅ Correct — use UseWhen for conditional middleware that should still reach the main endpoint mapping.
Mistake 2 — Putting Map/MapWhen after MapControllers (never matched)
❌ Wrong — MapControllers is a terminal middleware; it handles all matched routes before Map is reached.
✅ Correct — always put path-based branching before MapControllers.