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);
Note: This middleware should typically be placed before the routing middleware.

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();
Note: This middleware should be placed after 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:

  1. Define the Middleware Class: Create a class with a constructor that accepts RequestDelegate and an InvokeAsync method.
  2. Add to the Pipeline: Register your middleware in the Configure method of Startup.cs using app.UseMiddleware<YourMiddlewareClass>() or app.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
}