ASP.NET Core Middleware
Middleware is the backbone of the ASP.NET Core request processing pipeline. It's a component that plugs into the application pipeline and has access to the request and response objects. Middleware components can execute code, call the next middleware in the pipeline, and modify the request and response as they flow through the pipeline.
Key Concept: Think of the middleware pipeline as a series of steps that a request must pass through before reaching the application's core logic and before the response is sent back to the client.
The Request Pipeline
In ASP.NET Core, the request pipeline is configured in the Startup.cs
file within the Configure
method. This method takes an IApplicationBuilder
and an IWebHostEnvironment
as arguments.
The IApplicationBuilder
interface provides methods to:
- Add middleware to the pipeline using
Use[MiddlewareName]()
extension methods. - Build the pipeline.
The order in which you add middleware is crucial. The pipeline executes in the order the middleware is configured.
Common Middleware Components
ASP.NET Core provides a rich set of built-in middleware components:
-
UseStaticFiles()
: Serves static files (HTML, CSS, JavaScript, images) from the web root directory. -
UseRouting()
: Implements the routing logic, matching the incoming request to an endpoint. -
UseAuthentication()
: Authenticates the user based on configured schemes. -
UseAuthorization()
: Authorizes the user to access the requested resource. -
UseEndpoints()
: Executes the endpoint selected by the routing middleware. -
UseDeveloperExceptionPage()
: Displays detailed exception information in development environments. -
UseExceptionHandler()
: Handles exceptions in production environments. -
UseCors()
: Configures Cross-Origin Resource Sharing (CORS) policies. -
UseSwagger()
andUseSwaggerUI()
: Integrates Swagger for API documentation.
Example Configuration in Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"));
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts(); // HTTP Strict Transport Security
}
app.UseHttpsRedirection(); // Redirect HTTP requests to HTTPS
app.UseStaticFiles(); // Serve static files
app.UseRouting(); // Enable routing
app.UseAuthentication(); // Enable authentication
app.UseAuthorization(); // Enable authorization
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers(); // For API controllers
});
}
Important: The order of UseRouting()
and UseEndpoints()
is fixed. UseRouting()
must come before any middleware that depends on routing information (like UseAuthorization()
), and UseEndpoints()
must come after those, as it executes the endpoint chosen by routing.
Creating Custom Middleware
You can create your own custom middleware to encapsulate specific logic. There are several ways to do this:
1. Using an Anonymous Function (Inline Middleware)
This is useful for simple, single-purpose middleware.
app.Use(async (context, next) =>
{
// Code to execute before the next middleware
await context.Response.WriteAsync("Hello from custom middleware! ");
await next(); // Call the next middleware in the pipeline
// Code to execute after the next middleware has finished
await context.Response.WriteAsync(" Returning from custom middleware.");
});
2. Using a Class with a Invoke
or InvokeAsync
Method
This is more organized for complex logic.
// CustomMiddleware.cs
public class CustomMiddleware
{
private readonly RequestDelegate _next;
public CustomMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
// Logic before next middleware
await context.Response.WriteAsync("Before Custom Middleware
");
await _next(context); // Call the next middleware
// Logic after next middleware
await context.Response.WriteAsync("After Custom Middleware
");
}
}
// Startup.cs - Configure method
app.UseMiddleware<CustomMiddleware>();
3. Using an Extension Method
This is the most common and recommended approach for making custom middleware reusable and discoverable.
// MiddlewareExtensions.cs
public static class MiddlewareExtensions
{
public static IApplicationBuilder UseMyCustomMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<CustomMiddleware>();
}
}
// Startup.cs - Configure method
app.UseMyCustomMiddleware();
Tip: Consider using dependency injection to inject services into your custom middleware's constructor if it needs to access other parts of your application.
Middleware Order and Execution Flow
The order of middleware in the Configure
method defines how requests are processed and how responses are formed.
- A request enters the pipeline at the first middleware configured.
-
Each middleware can perform actions before or after calling the
next
delegate. -
If a middleware doesn't call
next
, it effectively short-circuits the pipeline for that request. - The request continues down the pipeline until it reaches the final destination (e.g., a controller action or Razor Page).
- The response then travels back up the pipeline, with each middleware having a chance to process it.
Conclusion
Understanding and effectively using ASP.NET Core middleware is fundamental to building robust and scalable web applications. By composing middleware, you can implement cross-cutting concerns such as authentication, logging, error handling, routing, and more, in a clean and modular fashion.