.NET Middleware Pipeline
The ASP.NET Core middleware pipeline is a fundamental concept that controls how HTTP requests are processed in your application. Understanding how it works is crucial for building robust APIs.
What is Middleware?
Middleware is software that's assembled into an application pipeline to handle requests and responses. Each component:
- Chooses whether to pass the request to the next component in the pipeline
- Can perform work before and after the next component in the pipeline
Think of middleware as a series of layers that each HTTP request must pass through, with each layer having the opportunity to process the request on the way in and the response on the way out.
The Pipeline Concept
Request → [Middleware 1] → [Middleware 2] → [Middleware 3] → Endpoint
↓ ↓ ↓ ↓
Response ← [Middleware 1] ← [Middleware 2] ← [Middleware 3] ← Endpoint
Each middleware component can:
- Pass the request to the next middleware using
next() - Short-circuit the pipeline by not calling
next() - Process before calling
next()(on the request path) - Process after calling
next()(on the response path)
How Middleware is Configured
In ASP.NET Core, middleware is configured in the Program.cs file using the WebApplicationBuilder. Here's a typical setup:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container
builder.Services.AddControllers();
var app = builder.Build();
// Configure the HTTP request pipeline
app.UseHttpsRedirection(); // Middleware 1
app.UseAuthentication(); // Middleware 2
app.UseAuthorization(); // Middleware 3
app.MapControllers(); // Endpoint routing
app.Run();
Order Matters!
The order in which you add middleware is critical. Middleware executes in the order it's added. For example:
// ✅ Correct order
app.UseAuthentication(); // First, identify the user
app.UseAuthorization(); // Then, check permissions
// ❌ Wrong order
app.UseAuthorization(); // Can't authorize without knowing who the user is!
app.UseAuthentication();
Common Middleware Components
1. Exception Handling Middleware
Should be one of the first middleware components to catch exceptions from later middleware.
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/error");
}
2. HTTPS Redirection
Redirects HTTP requests to HTTPS.
app.UseHttpsRedirection();
3. Static Files
Serves static files like images, CSS, and JavaScript.
app.UseStaticFiles();
4. Routing
Matches requests to endpoints.
app.UseRouting();
5. CORS
Enables Cross-Origin Resource Sharing.
app.UseCors("MyPolicy");
6. Authentication & Authorization
Authenticates users and checks permissions.
app.UseAuthentication();
app.UseAuthorization();
7. Endpoint Routing
Maps controllers, Minimal APIs, or other endpoints.
app.MapControllers();
// or
app.MapGet("/hello", () => "Hello World!");
Creating Custom Middleware
You can create your own middleware in three ways:
1. Inline Middleware (Use)
app.Use(async (context, next) =>
{
// Do work before the next middleware
Console.WriteLine("Before next middleware");
await next(context);
// Do work after the next middleware
Console.WriteLine("After next middleware");
});
2. Terminal Middleware (Run)
This doesn't call next() - it terminates the pipeline.
app.Run(async context =>
{
await context.Response.WriteAsync("Pipeline ends here!");
});
3. Middleware Class
For more complex middleware, create a dedicated class:
public class RequestLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestLoggingMiddleware> _logger;
public RequestLoggingMiddleware(
RequestDelegate next,
ILogger<RequestLoggingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
// Log the incoming request
_logger.LogInformation(
"Request: {Method} {Path}",
context.Request.Method,
context.Request.Path);
// Call the next middleware
await _next(context);
// Log the response
_logger.LogInformation(
"Response: {StatusCode}",
context.Response.StatusCode);
}
}
// Extension method for easy registration
public static class RequestLoggingMiddlewareExtensions
{
public static IApplicationBuilder UseRequestLogging(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestLoggingMiddleware>();
}
}
// Usage in Program.cs
app.UseRequestLogging();
Recommended Middleware Order
Here's a recommended order for common middleware components:
- Exception handling (
UseDeveloperExceptionPageorUseExceptionHandler) - HTTPS Redirection (
UseHttpsRedirection) - Static Files (
UseStaticFiles) - Routing (
UseRouting) - CORS (
UseCors) - Authentication (
UseAuthentication) - Authorization (
UseAuthorization) - Custom middleware (your business logic)
- Endpoints (
MapControllers,MapGet, etc.)
Key Takeaways
- Middleware forms a pipeline through which every HTTP request flows
- Each middleware can process the request going in and the response coming out
- Order matters - middleware executes in the order it's registered
- Middleware can short-circuit the pipeline by not calling
next() - You can create custom middleware for cross-cutting concerns like logging, timing, etc.
- Understanding the pipeline helps you debug issues and optimize performance