Short-Circuiting and Terminal Middleware

End requests early with ShortCircuit, MapShortCircuit and terminal middleware, and know which middleware and policies those requests then skip.

Syntaxapp.MapShortCircuit(404, "robots.txt", "favicon.ico"); app.MapGet("/health/live", () => "ok").ShortCircuit();

Short-circuiting means producing a response without running the rest of the pipeline. It saves work for requests that need none of it, such as crawler hits on /robots.txt, browsers asking for /favicon.ico or load balancer probes. ASP.NET Core offers three ways to do it, and each skips a different amount.

The three tools

ToolWhat runsWhat is skipped
Middleware that does not call nextEverything registered before itEverything after it, including the endpoint
.ShortCircuit() on an endpointMiddleware before UseRouting, then the handler, executed inside the routing middlewareAll middleware between routing and endpoints: auth, CORS, rate limiting, your own
app.MapShortCircuit(404, "robots.txt", "favicon.ico")Same as aboveSame, and no handler at all: it writes the status code for any path under those prefixes

ShortCircuit(int? statusCode) optionally sets the status before the handler runs. Remember where WebApplication puts routing: if you never call UseRouting, it runs first, so a short-circuited endpoint skips all of your middleware, including UseExceptionHandler and request logging. Call UseRouting explicitly after the middleware that must still run.

The safety check

Because a short-circuited endpoint never reaches the authorization or CORS middleware, the routing middleware refuses to execute one that carries authorization, CORS or antiforgery metadata. A request to it throws InvalidOperationException: Endpoint ... contains authorization metadata, but this endpoint is marked with short circuit and the client gets 500. That is deliberate: the alternative would be serving a protected endpoint to anonymous callers.

Returning early from your own middleware

Write the status (and a body if useful) and return without calling next. Typical cases: a maintenance switch returning 503 with Retry-After, rejecting requests without a required tenant header, or answering a webhook signature failure with 401. Set the response before writing the body, and do not call next after you have started the response.

When not to

  • Anything that needs authentication, authorization, CORS or rate limiting.
  • Readiness probes that should run the same health check pipeline as real traffic; short-circuit liveness, not readiness, if anything.
  • Requests you want counted in your own request logs or metrics middleware: they disappear from those numbers.
  • Do not reach for it to save microseconds on ordinary API calls; the gain only matters for high-volume junk traffic.

The usual team decision: MapShortCircuit(404, "robots.txt", "favicon.ico", "apple-touch-icon.png") on pure APIs, and ShortCircuit() on an anonymous liveness endpoint, nothing else.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Answer /robots.txt and /favicon.ico with 404 on a pure JSON API so crawler noise skips authentication and request logging.
  • Return 503 with Retry-After: 120 for /orders while a maintenance flag is on, without calling the rest of the pipeline.
  • Serve an anonymous /health/live probe with ShortCircuit() so a slow downstream middleware cannot fail liveness.

More examples

Pitfall: short-circuiting a protected endpoint

The routing middleware checks short-circuited endpoints for authorization, CORS and antiforgery metadata and throws rather than silently skipping those checks. The error appears per request, not at start-up, so a test that calls each protected route is what catches it before deployment.

Example · csharp
using Microsoft.AspNetCore.Authentication.Cookies;

var builder = WebApplication.CreateSlimBuilder(args);
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();
builder.Services.AddAuthorization();
var app = builder.Build();

// Wrong: authorization middleware never sees this endpoint, so routing refuses to run it
// and every call returns 500 with "contains authorization metadata, but this endpoint
// is marked with short circuit".
// app.MapGet("/admin/metrics", () => "...").RequireAuthorization().ShortCircuit();

// Right: protected endpoints go through the full pipeline.
app.MapGet("/admin/metrics", () => TypedResults.Ok(new { openOrders = 42 }))
   .RequireAuthorization();

// Only anonymous, side-effect-free endpoints are candidates.
app.MapGet("/health/live", () => TypedResults.Ok("alive")).ShortCircuit();

app.Run();

Keeping the exception handler in front of short-circuited endpoints

Without the explicit <code>UseRouting</code>, WebApplication would add routing at the start of the pipeline and a short-circuited endpoint would bypass even <code>UseExceptionHandler</code>, turning a thrown exception into a bare 500. Placing routing yourself decides exactly which middleware short-circuited requests still pass through.

Example · csharp
var builder = WebApplication.CreateSlimBuilder(args);
builder.Services.AddProblemDetails();
var app = builder.Build();

// These run before routing, so they also apply to short-circuited endpoints.
app.UseExceptionHandler();
app.UseHttpsRedirection();

// Explicit routing: endpoints marked ShortCircuit execute here.
app.UseRouting();

// Skipped by short-circuited endpoints.
app.Use(async (context, next) =>
{
    context.Response.Headers["X-Api-Version"] = "2026-10";
    await next(context);
});

app.MapGet("/robots.txt", () => Results.Text("User-agent: *\nDisallow: /", "text/plain"))
   .ShortCircuit();
app.MapGet("/products", () => TypedResults.Ok(new[] { "KET-0042" }));

app.Run();

Requests and expected responses

An <code>.http</code> file documents what each path should return and doubles as a manual check after deployment. <code>MapShortCircuit</code> matches the listed prefixes and anything under them, so <code>/robots.txt</code> and <code>/favicon.ico</code> get a 404 without allocating a handler or touching authentication.

Example · http
### Crawler noise: answered by MapShortCircuit, never logged, never authenticated
GET http://localhost:8080/robots.txt
# 404, empty body

GET http://localhost:8080/favicon.ico
# 404, empty body

### Liveness probe: ShortCircuit endpoint, cheap and anonymous
GET http://localhost:8080/health/live
# 200 alive

### Normal API call: full pipeline
GET http://localhost:8080/orders/1001
Authorization: Bearer <token-from-identity-provider>
# 200 {"id":1001,"status":"Paid"}

Discussion

  • Be the first to comment on this lesson.