Middleware & Request Pipeline in ASP.NET Core
Learn how ASP.NET Core handles incoming HTTP requests through middleware, what the request pipeline really does, and how to build custom middleware components that integrate cleanly with the framework.
What is Middleware?
Middleware is software that sits between the web server and the application code. In ASP.NET Core, middleware components inspect, modify, or short-circuit the HTTP request and response as they flow through the request pipeline.
Every request passes through a chain of middleware. Each component decides whether to pass control to the next component or to generate a response immediately.
public class RequestTimingMiddleware
{
private readonly RequestDelegate _next;
public RequestTimingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var start = DateTime.UtcNow;
await _next(context);
var elapsed = DateTime.UtcNow - start;
context.Response.Headers["X-Request-Duration"] = elapsed.TotalMilliseconds + "ms";
}
}
The ASP.NET Core Request Pipeline
The pipeline is a sequence of middleware registered in Program.cs. It starts when the server receives a request
and ends when the application sends a response back to the client.
- Server receives the raw HTTP request.
- Host configures services and environment.
- Middleware components inspect and handle the request.
- Routing selects the endpoint.
- Endpoint execution generates the response.
- Response flows back through the middleware chain.
💡 Note: Middleware components are executed in the order they are registered. The order determines whether request or response logic runs first.
Built-in Middleware Examples
ASP.NET Core includes many built-in middleware components that handle common concerns such as static files, authentication, routing, exception handling, and authorization.
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
Each call adds a middleware delegate to the pipeline. For example, UseRouting matches the request to a route,
while UseAuthorization checks policies after routing has identified an endpoint.
Middleware Order & Execution
The order of middleware matters because request logic runs from top to bottom and response logic runs from bottom to top. That means you should register authentication before authorization and exception handling as early as possible.
app.UseExceptionHandler("/error");
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
If middleware does not call await _next(context), it short-circuits the pipeline and the request stops there.
That is useful for caching, maintenance mode, or HTTP redirects.
Writing Custom Middleware
Custom middleware is a class that accepts a RequestDelegate and exposes an InvokeAsync method.
It can read or modify HttpContext and control whether the next component runs.
public class RequestLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public RequestLoggingMiddleware(RequestDelegate next, ILogger logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
_logger.LogInformation("Incoming request: {Method} {Path}", context.Request.Method, context.Request.Path);
await _next(context);
_logger.LogInformation("Response status: {StatusCode}", context.Response.StatusCode);
}
}
This middleware logs request and response details. You can also write middleware that handles authentication, rate limiting, request validation, or response compression.
Using Middleware in Program.cs
Register middleware in the correct order in the application builder. Use extension methods to keep registration clean.
public static class RequestLoggingMiddlewareExtensions
{
public static IApplicationBuilder UseRequestLogging(this IApplicationBuilder builder)
{
return builder.UseMiddleware();
}
}
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseExceptionHandler("/error");
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseRequestLogging();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
Branching & Short-Circuiting
You can branch the pipeline with Map and stop execution early by not calling _next.
This allows you to handle certain paths separately or return a response directly.
app.Map("/health", builder =>
{
builder.Run(async context =>
{
await context.Response.WriteAsync("Healthy");
});
});
app.Use(async (context, next) =>
{
if (context.Request.Path.StartsWithSegments("/api") && !context.Request.Headers.ContainsKey("X-Api-Key"))
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
await context.Response.WriteAsync("API key required.");
return;
}
await next();
});
Branching is useful for health checks, maintenance endpoints, or request filtering. When middleware returns without calling
next(), the pipeline ends and the response is sent immediately.
Summary
ASP.NET Core middleware forms a pipeline that processes requests and responses in order.
Middleware order matters: request logic runs top-down and response logic runs bottom-up.
Custom middleware is a simple class with a RequestDelegate dependency and an InvokeAsync method.
Use extension methods to register middleware cleanly and keep Program.cs readable.
Branching and short-circuiting let you handle special endpoints and stop processing when needed.