Introduction to ASP.NET Core Middleware
Middleware components form the backbone of the ASP.NET Core request processing pipeline. They are responsible for handling incoming HTTP requests and outgoing HTTP responses.
Each middleware component has the opportunity to:
- Execute code before the rest of the pipeline.
- Call the next middleware in the pipeline.
- Execute code after the next middleware has completed.
- Short-circuit the pipeline by not calling the next middleware.
The Request Pipeline
The ASP.NET Core request pipeline is a series of middleware components chained together. When a request arrives, it travels down the pipeline, with each middleware having a chance to process it. When the response is generated, it travels back up the pipeline in reverse order.
The order in which middleware is registered is crucial. For example, routing must typically occur before endpoint selection, and authentication should often happen before authorization.
The pipeline is configured in the Startup.cs
file within the Configure
method using the IApplicationBuilder
interface.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Middleware are added here in order
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello, World!");
});
});
}
Core Concepts
IMiddleware
Interface
The IMiddleware
interface defines a single method, InvokeAsync
, which is responsible for processing the request.
Task InvokeAsync(HttpContext context, RequestDelegate next);
HttpContext
provides access to the request and response objects. RequestDelegate next
represents the delegate for the next middleware in the pipeline.
Invokable Middleware
A simpler way to implement middleware is by creating a class with an InvokeAsync
method that accepts an HttpContext
and a RequestDelegate
.
Alternatively, middleware can be implemented as a delegate function (often a lambda expression) directly when configuring the pipeline.
// Using a class
public class MyCustomMiddleware
{
private readonly RequestDelegate _next;
public MyCustomMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
// Pre-processing logic
await _next(context); // Call the next middleware
// Post-processing logic
}
}
// Usage in Startup.cs
app.UseMiddleware();
// Using a lambda
app.Use(async (context, next) =>
{
// Pre-processing logic
await next(context); // Call the next middleware
// Post-processing logic
});
Options Configuration
Many built-in middleware components allow for configuration through options classes. These options are typically registered with the Dependency Injection (DI) container and accessed by the middleware.
Example: Configuring the Static Files Middleware.
services.AddDirectoryBrowser(); // Example for DirectoryBrowserOptions
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "wwwroot")),
RequestPath = "/static"
});
Common Middleware Components
Static Files Middleware
Serves static files (HTML, CSS, JS, images) from a designated directory (usually wwwroot
).
app.UseStaticFiles(StaticFileOptions options = null);
Routing Middleware
Responsible for matching the incoming request URL to an endpoint defined in the application.
app.UseRouting();
This middleware populates the HttpContext.Request.RouteValues
property.
Authentication Middleware
Handles authentication logic, typically by inspecting the request (e.g., cookies, tokens) and setting the authenticated user identity.
app.UseAuthentication();
Requires authentication services to be configured in Startup.ConfigureServices
.
Authorization Middleware
Enforces access control based on the authenticated user's identity and defined policies.
app.UseAuthorization();
This middleware should be placed after UseAuthentication
.
Exception Handling Middleware
Catches exceptions thrown by downstream middleware and handles them gracefully, often by returning an appropriate HTTP error response.
app.UseExceptionHandler(configureOptions);
app.UseDeveloperExceptionPage(); // For development environments
CORS Middleware
Enables Cross-Origin Resource Sharing, allowing web pages from different domains to make requests to your application.
app.UseCors(configureOptions);
Requires CORS services to be configured in Startup.ConfigureServices
.
HTTP Strict Transport Security (HSTS) Middleware
Instructs browsers to only communicate with the server using HTTPS.
app.UseHsts();
UseHttpsRedirection
and before other middleware that might modify the response headers.
HTTPS Redirection Middleware
Redirects all HTTP requests to HTTPS.
app.UseHttpsRedirection();
Creating Custom Middleware
You can create your own custom middleware to implement specific functionalities:
- Define the Middleware Class: Create a class with a constructor that accepts
RequestDelegate
and anInvokeAsync
method. - Add to the Pipeline: Register your middleware in the
Configure
method ofStartup.cs
usingapp.UseMiddleware<YourMiddlewareClass>()
orapp.Use(async (context, next) => { ... })
.
Consider the placement of your custom middleware in the pipeline based on its functionality.
// Example: Logging Middleware
public class LoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public LoggingMiddleware(RequestDelegate next, ILogger logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
_logger.LogInformation($"Request started: {context.Request.Method} {context.Request.Path}");
await _next(context); // Pass the request to the next middleware
_logger.LogInformation($"Request finished: {context.Response.StatusCode}");
}
}
// In Startup.cs:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// ... other middleware
app.UseMiddleware();
// ... rest of the pipeline
}