Middleware in ASP.NET Core: Beginner’s Complete Guide
Learn everything about Middleware in ASP.NET Core! Understand request pipelines, built-in and custom middleware, execution order, and best practices for building scalable Web APIs.
If you’ve ever built an ASP.NET Core application, you might have heard about middleware — a core concept that powers the request and response pipeline. But what exactly is middleware, and why is it so important?
In simple terms, middleware is a series of components that handle HTTP requests and responses in your application. Each component can inspect, modify, or even terminate a request before it reaches your core application logic.
This guide will help you understand how middleware works, the difference between built-in and custom middleware, and best practices for building efficient Web APIs.
What is Middleware in ASP.NET Core?
Middleware is essentially software that sits between the client request and your application’s core logic. It allows you to:
- Log requests and responses
- Handle authentication and authorization
- Transform requests and responses
- Handle errors
- Serve static files
- Implement caching and compression
When a request enters your application, it passes through a pipeline of middleware components. Each component decides whether to process the request, pass it along, or short-circuit it by generating a response immediately.
How Middleware Works
Each middleware component follows this simple pattern:
- Receive the HttpContext object, which contains all request and response information.
- Process the request (e.g., check authentication, log info).
- Call the next middleware in the pipeline using
await next(), or terminate the request by sending a response immediately.
The order of middleware is crucial because it affects how requests are processed.
Middleware Execution Order Example
app.Use(async (context, next) =>
{
Console.WriteLine("Middleware 1: Incoming request");
await next();
Console.WriteLine("Middleware 1: Outgoing response");
});
app.Use(async (context, next) =>
{
Console.WriteLine("Middleware 2: Incoming request");
await next();
Console.WriteLine("Middleware 2: Outgoing response");
});
app.Run(async (context) =>
{
Console.WriteLine("Middleware 3: Handling request and terminating pipeline");
await context.Response.WriteAsync("Hello, world!");
});
Expected Flow:
Middleware 1: Incoming request
Middleware 2: Incoming request
Middleware 3: Handling request and terminating pipeline
Middleware 2: Outgoing response
Middleware 1: Outgoing response
- Middleware executes in the order they are added in
Program.cs. app.Use()allows the request to continue to the next middleware.app.Run()short-circuits the pipeline and ends processing.- Order matters—placing authentication middleware before authorization middleware ensures users are authenticated before checking permissions.
Core Concepts: RequestDelegate & HttpContext
RequestDelegate
A RequestDelegate is a function that processes HTTP requests. It can:
- Modify the request
- Generate a response immediately
- Pass control to the next middleware
Three common ways to define request delegates:
1. Inline Middleware (Lambda)
app.Use(async (context, next) =>
{
Console.WriteLine("Incoming request: " + context.Request.Path);
await next();
Console.WriteLine("Outgoing response: " + context.Response.StatusCode);
});
2. Short-Circuit Middleware
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from the last middleware!");
});
3. Middleware Classes
public class CustomMiddleware
{
private readonly RequestDelegate _next;
public CustomMiddleware(RequestDelegate next) => _next = next;
public async Task InvokeAsync(HttpContext context)
{
Console.WriteLine("Custom Middleware Executing...");
await _next(context);
Console.WriteLine("Custom Middleware Finished.");
}
}
app.UseMiddleware<CustomMiddleware>(); //register it in program.cs
HttpContext
The HttpContext object contains:
- Request info (
context.Request.Path, headers, query) - Response info (
context.Response.StatusCode, body) - Authentication and authorization info (
context.User.Identity)
Commonly Used Properties of HttpContext
- Accessing Request Data
string method = context.Request.Method;
string path = context.Request.Path;
string query = context.Request.QueryString.ToString();
- Reading Request Headers
string userAgent = context.Request.Headers["User-Agent"];
- Handling Query Parameters
string name = context.Request.Query["name"];
- Setting Response Data
context.Response.StatusCode = 200;
await context.Response.WriteAsync("Hello, World!");
- Checking Authentication & Authorization
bool isAuthenticated = context.User.Identity.IsAuthenticated;
How Request Delegate and HttpContext Work Together
Each request delegate receives an HttpContext, processes it, and either:
- Calls the next middleware in the pipeline (
await next();) - Returns a response immediately (
context.Response.WriteAsync("Hello!");)
Built-In Middlewares You Should Know
ASP.NET Core provides many useful built-in middleware components:
- Exception Handling:
app.UseExceptionHandler("/Home/Error")This middleware captures unhandled exceptions and provides a centralized mechanism for handling errors. It ensures that errors are logged properly and can return custom error responses. - Routing:
app.UseRouting()This middleware handles routing of requests to the appropriate controller and action methods based on the URL. - Authentication & Authorization:
app.UseAuthentication(); app.UseAuthorization();This middleware handles authentication and authorization of requests based on the user's identity and permissions. - Static Files:
app.UseStaticFiles()This middleware serves static files (e.g., images, CSS, JavaScript) from the specified directory. - CORS:
app.UseCors()This middleware handles Cross-Origin Resource Sharing (CORS) requests. - Response Compression:
app.UseResponseCompression()This middleware compresses responses to reduce bandwidth usage and improve performance. - Session Management:
app.UseSession()This middleware manages user sessions and provides session state management. - HTTPS Redirection:
app.UseHttpsRedirection()This middleware redirects HTTP requests to HTTPS. - Request Logging:
app.UseSerilogRequestLogging()This middleware logs incoming requests and responses for debugging and monitoring purposes. - Endpoint Mapping: The final step in request processing, this middleware matches incoming requests to their respective controllers, Razor pages, or minimal API endpoints.
app.UseEndpoints(endpoints => { endpoints.MapControllers(); endpoints.MapRazorPages(); });
Creating Custom Middleware
Sometimes built-in middleware isn’t enough. Custom middleware allows you to add specific functionality to your application.
A middleware component in ASP.NET Core must:
- Accept a
RequestDelegatein its constructor. - Implement an
InvokeorInvokeAsyncmethod that processes the request.
Step 1: Create Middleware 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)
{
_logger.LogInformation($"Incoming Request: {context.Request.Method} {context.Request.Path}");
await _next(context);
_logger.LogInformation($"Response Status Code: {context.Response.StatusCode}");
}
}
Step 2: Register Middleware
app.UseMiddleware<RequestLoggingMiddleware>();
You can also create extension methods for cleaner registration:
public static class MiddlewareExtensions
{
public static IApplicationBuilder UseRequestLogging(this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestLoggingMiddleware>();
}
}
app.UseRequestLogging();
Short-Circuiting the Pipeline
Sometimes you want to stop the request early. For example, during maintenance mode:
public class MaintenanceMiddleware
{
private readonly RequestDelegate _next;
public MaintenanceMiddleware(RequestDelegate next) => _next = next;
public async Task InvokeAsync(HttpContext context)
{
context.Response.StatusCode = 503;
await context.Response.WriteAsync("Service is under maintenance.");
}
}
app.UseMiddleware<MaintenanceMiddleware>();
Best Practices for Middleware in Web APIs
- Keep Middleware Lightweight – Focus on a single responsibility.
- Order Matters – Example order:
- Exception Handling → HTTPS → Routing → Authentication → Authorization → Custom Middleware → Endpoints
- Use Built-in Middleware When Possible – Avoid reinventing the wheel.
- Avoid Blocking Calls – Always use async/await.
- Short-Circuit When Necessary – For caching or maintenance.
- Use Extensions for Clean Code – Keeps Program.cs readable.
- Log Requests and Responses – Useful for debugging.
- Avoid Overuse – Middleware should handle cross-cutting concerns, not specific controller logic.
Recommended Middleware Execution Order
var app = builder.Build();
app.UseExceptionHandler("/error"); // 1. Global Exception Handling
app.UseHttpsRedirection(); // 2. Enforce HTTPS
app.UseRouting(); // 3. Routing
app.UseCors(); // 4. Enable CORS
app.UseAuthentication(); // 5. Authenticate Users
app.UseAuthorization(); // 6. Check Permissions
app.UseMiddleware<CustomMiddleware>(); // 7. Custom Middleware
app.UseEndpoints(endpoints => // 8. Map Endpoints
{
endpoints.MapControllers();
});
app.Run();
Summary
Middleware is the backbone of ASP.NET Core request processing. It lets you handle logging, authentication, error handling, and request transformations efficiently.
Key takeaways:
- Middleware processes requests and responses in a pipeline.
- Built-in middleware simplifies common tasks.
- Custom middleware allows flexible, reusable functionality.
- Execution order and best practices matter for performance and security.
- Short-circuiting improves efficiency in certain scenarios.
Mastering middleware is a critical step for any ASP.NET Core developer. By understanding and leveraging middleware, you can build efficient, secure, and maintainable Web APIs.
