The Request Pipeline
π― What You'll Learn
- What the request pipeline is
- How HTTP requests flow through ASP.NET Core
- Understanding middleware components
- The pipeline execution model
- Request and response flow
- Short-circuiting the pipeline
- Terminal vs non-terminal middleware
- Visualizing the pipeline
What is the Request Pipeline?
The request pipeline is a series of components (called middleware) that handle HTTP requests and responses in ASP.NET Core.
Every HTTP request that comes into your application flows through the pipeline. Each middleware component can:
- Process the request
- Pass it to the next middleware
- Process the response on the way back
- Short-circuit (stop) the pipeline
How the Pipeline Works
HTTP Request
β
βββββββββββββββββββββββββββββββββββββββββ
β Middleware 1 (e.g., Logging) β β Request processing
β β β
β Middleware 2 (e.g., Authentication) β β Request processing
β β β
β Middleware 3 (e.g., Routing) β β Request processing
β β β
β Endpoint (Controller/Minimal API) β β Generate response
β β β
β Middleware 3 β β Response processing
β β β
β Middleware 2 β β Response processing
β β β
β Middleware 1 β β Response processing
βββββββββββββββββββββββββββββββββββββββββ
β
HTTP Response
The Flow
- Request comes in: HTTP request enters the pipeline
- Forward pass: Each middleware processes the request in order
- Endpoint: Final middleware generates a response
- Backward pass: Response flows back through middleware in reverse
- Response sent: HTTP response sent to client
Basic Pipeline Example
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Middleware 1: Logging
app.Use(async (context, next) =>
{
Console.WriteLine("[1] Before next");
await next(); // Call next middleware
Console.WriteLine("[1] After next");
});
// Middleware 2: Authentication
app.Use(async (context, next) =>
{
Console.WriteLine("[2] Before next");
await next();
Console.WriteLine("[2] After next");
});
// Middleware 3: Endpoint (Terminal)
app.Run(async context =>
{
Console.WriteLine("[3] Handling request");
await context.Response.WriteAsync("Hello World!");
});
app.Run();
Output
[1] Before next
[2] Before next
[3] Handling request
[2] After next
[1] After next
The request flows forward (1 β 2 β 3), then the response flows backward (3 β 2 β 1). This is the "onion" model.
Middleware Methods
1. app.Use() - Non-Terminal
app.Use(async (context, next) =>
{
// Do something before
await next(); // Call next middleware
// Do something after
});
Use: Can call the next middleware. Non-terminal.
2. app.Run() - Terminal
app.Run(async context =>
{
// Handle request - no next middleware
await context.Response.WriteAsync("Done!");
});
Run: Terminal middleware. Ends the pipeline. No next parameter.
3. app.Map() - Branching
app.Map("/api", apiApp =>
{
apiApp.Run(async context =>
{
await context.Response.WriteAsync("API endpoint");
});
});
Map: Branch the pipeline based on request path.
4. app.MapWhen() - Conditional Branching
app.MapWhen(
context => context.Request.Query.ContainsKey("debug"),
debugApp =>
{
debugApp.Run(async context =>
{
await context.Response.WriteAsync("Debug mode");
});
});
MapWhen: Branch based on a condition.
Short-Circuiting the Pipeline
Middleware can short-circuit by not calling next():
app.Use(async (context, next) =>
{
if (!context.Request.Headers.ContainsKey("Authorization"))
{
// Short-circuit! Don't call next()
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Unauthorized");
return; // Stop here!
}
await next(); // Continue if authorized
});
Short-circuit when:
- Request is unauthorized
- Request is invalid
- Response can be served from cache
- Static file can be served
HttpContext
Every middleware receives an HttpContext object that contains:
| Property | Description |
|---|---|
Request |
HTTP request details (headers, body, query, etc.) |
Response |
HTTP response to send back |
User |
Authenticated user information |
Items |
Per-request data storage |
RequestServices |
DI service provider (scoped) |
Session |
User session data |
app.Use(async (context, next) =>
{
// Read request
var path = context.Request.Path;
var method = context.Request.Method;
// Store data for later middleware
context.Items["RequestTime"] = DateTime.UtcNow;
await next();
// Modify response
context.Response.Headers["X-Custom-Header"] = "InvenTrack";
});
Complete InvenTrack Example
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
var app = builder.Build();
// 1. Request logging
app.Use(async (context, next) =>
{
var start = DateTime.UtcNow;
Console.WriteLine($"β {context.Request.Method} {context.Request.Path}");
await next();
var duration = (DateTime.UtcNow - start).TotalMilliseconds;
Console.WriteLine($"β {context.Response.StatusCode} ({duration:F2}ms)");
});
// 2. CORS
app.UseCors();
// 3. Authentication
app.UseAuthentication();
// 4. Authorization
app.UseAuthorization();
// 5. Routing & Endpoints
app.MapControllers();
app.Run();
Key Takeaways
- The request pipeline is a series of middleware components
- Requests flow forward, responses flow backward
app.Use()is non-terminal (calls next middleware)app.Run()is terminal (ends the pipeline)app.Map()branches the pipeline by pathapp.MapWhen()branches by condition- Middleware can short-circuit by not calling
next() HttpContextcontains request and response details- Middleware order matters (covered in Section 4)
- Each middleware can process both request and response
You now understand how the request pipeline works! In the next section, we'll explore Built-in Middlewareβthe middleware components that ASP.NET Core provides out of the box for common tasks like authentication, CORS, static files, and more.