Short-Circuiting & Branching
Use MapWhen, UseWhen, and terminal middleware to branch or terminate the pipeline based on request conditions.
Short-Circuiting & Branching is a free C# Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the C# Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Branching and Short-Circuiting
Not every request should traverse the full middleware pipeline. ASP.NET Core provides tools to branch (route subsets to different pipelines) and short-circuit (stop processing and return immediately).
Map: Permanent Branch by Path
Map creates a permanent branch — requests matching the prefix go into the branch and NEVER return to the main pipeline.
app.Map("/api", apiApp =>
{
apiApp.UseAuthentication();
apiApp.UseAuthorization();
apiApp.Run(async ctx =>
await ctx.Response.WriteAsync("API branch"));
});
// Requests to /api/... enter the branch above.
// Requests to /public/... skip the branch entirely.
app.Run(async ctx =>
await ctx.Response.WriteAsync("Main pipeline"));MapWhen: Branch on Predicate
MapWhen branches based on any condition (headers, query params, custom logic). Like Map, the branch is permanent — it doesn't rejoin the main pipeline.
// Branch when a specific header is present
app.MapWhen(
ctx => ctx.Request.Headers.ContainsKey("X-Internal"),
internalApp =>
{
internalApp.UseMiddleware<InternalApiMiddleware>();
internalApp.Run(async ctx =>
await ctx.Response.WriteAsync("Internal route"));
});
// Branch on query parameter
app.MapWhen(
ctx => ctx.Request.Query.ContainsKey("legacy"),
legacyApp => legacyApp.UseMiddleware<LegacyHandlerMiddleware>());UseWhen: Conditional Branch that Rejoins
UseWhen is like MapWhen but the branch rejoins the main pipeline after it completes. This lets you add middleware conditionally without forking.
// Log only for authenticated API calls; public routes not logged
app.UseWhen(
ctx => ctx.Request.Path.StartsWithSegments("/api")
&& ctx.User.Identity?.IsAuthenticated == true,
loggedApp =>
{
loggedApp.UseMiddleware<AuditMiddleware>();
});
// All requests continue here after the branch
app.UseRouting();
app.MapControllers();Short-Circuiting by Not Calling Next
Any middleware can short-circuit by writing the response and not calling next. The pipeline stops at that point and the response is returned to the client.
app.Use(async (ctx, next) =>
{
// IP allowlist check
var ip = ctx.Connection.RemoteIpAddress?.ToString();
var allowed = new[] { "127.0.0.1", "::1", "10.0.0.0/8" };
if (!IsAllowed(ip, allowed))
{
ctx.Response.StatusCode = 403;
await ctx.Response.WriteAsJsonAsync(
new { error = "Access denied from this IP" });
return; // short-circuit
}
await next(ctx); // proceed
});ShortCircuit() in .NET 8
In .NET 8, MapShortCircuit and the ShortCircuit() extension on endpoints provide a declarative, highly optimized short-circuit path.
// Reject known bad paths immediately — skip entire pipeline
app.MapShortCircuit(404, "robots.txt", "favicon.ico");
// Or on a specific endpoint:
app.MapGet("/health", () => "OK")
.ShortCircuit(); // skips auth, rate limiting, etc.
// 'Run' middleware also short-circuits:
app.Map("/old-api", old =>
old.Run(ctx =>
{
ctx.Response.StatusCode = 301;
ctx.Response.Headers.Location = "/api/v2";
return Task.CompletedTask;
}));Terminal Middleware vs Endpoint Routing
Before .NET 3, app.Run() was the only terminal option. Now, endpoint routing (MapGet, MapControllers) is preferred — it participates in authorization, rate limiting, and metadata.
// OLD: terminal middleware
app.Run(async ctx => {
if (ctx.Request.Path == "/ping")
await ctx.Response.WriteAsync("pong");
});
// PREFERRED: endpoint routing
app.MapGet("/ping", () => "pong")
.WithTags("Health")
.AllowAnonymous()
.RequireRateLimiting("basic");Path Matching Nuances
Map("/api") matches /api and /api/anything. The matched path is removed from Request.Path within the branch and placed in Request.PathBase.
app.Map("/api", apiApp =>
{
apiApp.Use(async (ctx, next) =>
{
// Path in branch: /users/42 (prefix removed)
Console.WriteLine(ctx.Request.Path); // /users/42
Console.WriteLine(ctx.Request.PathBase); // /api
await next(ctx);
});
apiApp.MapGet("/users/{id}", (int id) => id);
});Combining Branching Patterns
Real apps combine Map, UseWhen, and short-circuits to create layered pipelines: public routes are lightweight, API routes have auth, admin routes have extra checks.
// Public area — no auth
app.Map("/public", pub =>
{
pub.MapGet("/health", () => "OK");
pub.MapGet("/docs", () => "Documentation");
});
// API area — full auth + rate limiting
app.Map("/api", api =>
{
api.UseAuthentication();
api.UseAuthorization();
api.UseRateLimiter();
api.MapControllers();
});
// Admin area — require admin role
app.Map("/admin", admin =>
{
admin.UseAuthentication();
admin.UseAuthorization();
admin.MapControllers().RequireAuthorization("AdminPolicy");
});Real-World: A/B Testing Middleware
A/B testing middleware branches requests to different handlers based on a cookie or header, without modifying endpoint code.
app.UseWhen(
ctx => ctx.Request.Cookies.TryGetValue("ab-group", out var g) && g == "B",
betaApp =>
{
betaApp.Use(async (ctx, next) =>
{
ctx.Items["IsGroupB"] = true;
await next(ctx);
});
});
// Endpoint reads the flag:
app.MapGet("/products", (HttpContext ctx, ProductService svc) =>
{
bool isB = ctx.Items.ContainsKey("IsGroupB");
return isB ? svc.GetNewLayoutAsync() : svc.GetOldLayoutAsync();
});Quick Check
What is the key difference between MapWhen and UseWhen?
Recap: Short-Circuiting & Branching
Key takeaways:
Map: permanent path-based branch (no rejoin)MapWhen: permanent predicate-based branchUseWhen: conditional branch that rejoins the main pipeline- Short-circuit: don't call
next, write response and return ShortCircuit()(.NET 8): declarative, optimized terminal endpoints- Combine branching to build layered pipelines: public / API / admin
Frequently asked questions
Is the “Short-Circuiting & Branching” lesson free?
Yes — the full text of “Short-Circuiting & Branching” is free to read here on the web, and the C# Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the C# Academy course, upgrade to CoddyKit PRO.
What will I learn in “Short-Circuiting & Branching”?
Use MapWhen, UseWhen, and terminal middleware to branch or terminate the pipeline based on request conditions. You practise C# Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start C# Academy?
No prior experience is required. C# Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Short-Circuiting & Branching” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this C# Academy lesson?
Yes. Every C# Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- ASP.NET Core Pipeline Overview
- Writing Custom Middleware
- Short-Circuiting & Branching
- Middleware Ordering & Built-in Middleware