Request Lifecycle and the Middleware Pipeline
Every request walks through a chain, and the order of the chain is the logic
Guess first
Pick an answer. You will see if you were right at the end of the lesson.
A middleware calls await next(context) and then logs elapsed time. When does the log line run?
Real-world analogy
What is it?
The ASP.NET Core request pipeline is a chain of small components called middleware. Kestrel turns the incoming bytes into an HttpContext and passes it down the chain. Each component can inspect or change the request, call the next one, and then inspect or change the response on the way back. You build the chain in Program.cs, and the order of the lines is the order of execution.
Real-world relevance
A common production bug is a wrong order. If CORS runs after authorization, a 401 goes back without CORS headers. The browser then shows a confusing CORS error instead of the real one. If the exception handler is not first, errors thrown by earlier middleware are not caught. Teams also write small middleware for correlation ids, request timing and tenant lookup, because it runs for every request without touching controllers.
Key points
- Kestrel receives the request — Kestrel is the built-in web server. It reads raw bytes from the socket, builds an HttpContext, and hands it to the first middleware.
- Middleware is a chain — Each middleware gets the context and a next delegate. It can run code, call next, and then run more code after the rest of the chain finished.
- Before and after next — Code before await next(context) sees the request. Code after it sees the response on its way out. That is how timing and logging wrap everything.
- Order is the behavior — Middleware runs in the order you add it. A typical order is exception handler, HTTPS redirect, routing, CORS, authentication, authorization, endpoints.
- Short-circuiting — A middleware may skip next and write the response itself. Static files, CORS preflight and failed authorization all end the request early this way.
- Use, Run and Map — Use adds a middleware that can call next. Run is terminal and has no next. Map branches the pipeline for one path prefix.
- Custom middleware — For small jobs write an inline lambda with app.Use. For bigger ones write a class with an InvokeAsync(HttpContext) method and register it with UseMiddleware.
- Filters and binding come later — Routing picks an endpoint, then MVC model binding and filters run around the action. Middleware runs for requests across the whole app. Filters only run for requests that reach an MVC action.
Quick check
Answer from memory — scrolling up first makes it stick less.
What does it mean when a middleware short-circuits the pipeline?
Why does the order of app.Use... lines matter?
Code example
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();
builder.Services.AddAuthentication();
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseExceptionHandler(); // first: catches errors from everything below
app.UseHttpsRedirection();
// Inline middleware that wraps the rest of the pipeline
app.Use(async (context, next) =>
{
var watch = Stopwatch.StartNew();
await next(context); // everything below runs here
watch.Stop();
app.Logger.LogInformation("{Method} {Path} took {Elapsed} ms",
context.Request.Method, context.Request.Path, watch.ElapsedMilliseconds);
});
app.UseAuthentication(); // who are you?
app.UseAuthorization(); // what may you do?
app.MapGet("/ping", () => "pong");
app.Run();Line-by-line walkthrough
- 1. AddProblemDetails gives UseExceptionHandler a standard error body. It goes first, so an error thrown by any later middleware lands in it.
- 2. UseHttpsRedirection sends plain HTTP callers to the HTTPS address before any real work happens.
- 3. The app.Use lambda starts a stopwatch, then awaits next(context). Everything registered below it runs inside that await.
- 4. When next returns, the response is already built. The code after it stops the watch and logs method, path and milliseconds.
- 5. UseAuthentication reads the token and sets the user. UseAuthorization then decides if that user may continue, so it must come second.
- 6. MapGet is the endpoint at the end of the chain. Run starts listening.
Spot the bug
app.UseHttpsRedirection();
app.UseAuthorization(); // runs first
app.UseAuthentication(); // runs second
app.MapGet("/me", (ClaimsPrincipal user) => user.Identity?.Name)
.RequireAuthorization();Need a hint?
Show answer
Explain like I'm 5
Fun fact
Hands-on challenge
More resources
- ASP.NET Core Middleware (Microsoft Learn)
- Write custom ASP.NET Core middleware (Microsoft Learn)
Lock it in