Explain how to write custom middleware.

Question

Explain how to write custom middleware.

Brief Answer

Custom middleware in ASP.NET Core is a class designed to intercept and process HTTP requests and responses within the application’s request pipeline. It allows you to inject custom logic at specific points in the request lifecycle.

Key Components & Logic:

  1. The InvokeAsync Method: This is the heart of your middleware, where all custom logic resides. It takes two crucial parameters:

    • HttpContext context: Provides comprehensive access to the current HTTP request and response (headers, body, user, etc.).
    • RequestDelegate _next: Represents the next middleware component in the pipeline.
  2. Pipeline Control with _next:

    • Calling await _next(context) is crucial as it passes control to the next middleware in the pipeline, allowing the request to continue its journey.
    • Not calling _next(context) will short-circuit the pipeline. This is a powerful feature for scenarios like authentication (rejecting unauthenticated requests early) or rate limiting, preventing further processing.

Implementation Steps:

  1. Middleware Class Structure:

    Create a class (e.g., MyCustomMiddleware) with a constructor that accepts a RequestDelegate next (and can inject other services via Dependency Injection) and an InvokeAsync method containing your custom logic, typically before and/or after calling _next(context).

    public class MyCustomMiddleware
    {
        private readonly RequestDelegate _next;
        public MyCustomMiddleware(RequestDelegate next /*, OtherInjectedService service*/)
        {
            _next = next;
        }
        public async Task InvokeAsync(HttpContext context)
        {
            // Logic BEFORE calling the next middleware (e.g., logging request)
            await _next(context); // Call the next middleware
            // Logic AFTER calling the next middleware (e.g., modifying response)
        }
    }
  2. Registering the Middleware:

    Register your middleware in the application’s request pipeline, typically in Program.cs (or Startup.Configure), using app.UseMiddleware<YourMiddleware>().

    // In Program.cs
    app.UseMiddleware<MyCustomMiddleware>();
    // ... other middleware registrations

    Crucial Point: The order of registration matters significantly, as middleware executes sequentially. For instance, authentication typically runs before routing.

  3. Dependency Injection: Custom middleware fully supports ASP.NET Core’s DI, allowing you to inject services into its constructor for use within InvokeAsync.

Common Use Cases:

Custom middleware is invaluable for tasks not covered by built-in middleware, such as custom authentication/authorization, advanced logging/auditing, request/response modification (e.g., adding headers), and rate limiting.

Super Brief Answer

Custom middleware in ASP.NET Core is a class that processes HTTP requests/responses in the application’s pipeline. It enables injecting custom logic at specific points.

  • The core logic resides in the InvokeAsync method, accepting HttpContext (for request/response access) and RequestDelegate _next.
  • Calling await _next(context) passes control to the next middleware; *not* calling it short-circuits the pipeline (useful for authentication, rate limiting).
  • Implement by creating a class with a constructor injecting _next and an InvokeAsync method.
  • Register it in Program.cs using app.UseMiddleware<YourMiddleware>().
  • Order of registration is critical as middleware executes sequentially.
  • Used for custom authentication, logging, request modification, etc.

Detailed Answer

Custom middleware in ASP.NET Core is a class designed to handle HTTP requests and responses within the application’s request pipeline. It primarily implements an Invoke or InvokeAsync method, which contains the core logic for processing requests or responses. This method takes an HttpContext object, providing access to the current request and response, and a RequestDelegate named _next, which represents the next component in the pipeline.

Key Components of Custom Middleware

1. The Invoke or InvokeAsync Method

The Invoke or InvokeAsync method is the heart of your middleware, where all the custom logic resides. InvokeAsync is preferred for asynchronous operations, which are common in web applications. This method receives:

  • HttpContext context: Provides comprehensive access to the current HTTP request and response, allowing you to read headers, modify the response body, access user information, and more.
  • RequestDelegate next: This delegate represents the next middleware component in the request pipeline.

Explanation: Whether you’re checking headers, modifying the response, or logging, your core logic goes inside this method. For instance, to add a logging statement, you’d place it here. The HttpContext parameter gives you all the necessary information about the current request, while next allows you to pass control to the subsequent component in the pipeline.

2. The RequestDelegate next Parameter

Calling the _next(context) delegate is crucial as it invokes the next middleware component in the pipeline. This ensures the request continues its journey through the application.

  • Passing Control: If you intend for other middleware or the final request handler (like a controller) to process the request, you must call _next(context).
  • Short-Circuiting the Pipeline: Not calling _next(context) will short-circuit the pipeline. This means no further middleware or the final request handler will execute for that request. This behavior is incredibly useful for scenarios like:
    • Authentication: Rejecting unauthenticated requests early.
    • Authorization: Blocking unauthorized access.
    • Static File Serving: Directly serving files and preventing further processing.
    • Rate Limiting: Blocking excessive requests before they consume further resources.

Explanation: The next delegate acts like a “chain of responsibility.” Each middleware handles a specific aspect and then passes control to the next one. For example, logging middleware typically calls next to ensure the request is fully processed, whereas authentication middleware might only call next if the user is authenticated, otherwise short-circuiting with an error response (e.g., 401 Unauthorized).

Implementing Custom Middleware

1. Middleware Class Structure

A typical custom middleware class requires a constructor that accepts a RequestDelegate and an InvokeAsync method. You can also inject other services into the constructor via Dependency Injection.

public class MyCustomMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<MyCustomMiddleware> _logger; // Example: Injected service

    // Constructor: Injects the next middleware and any required services
    public MyCustomMiddleware(RequestDelegate next, ILogger<MyCustomMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    // InvokeAsync method: Contains the middleware logic
    public async Task InvokeAsync(HttpContext context)
    {
        // --- Logic BEFORE calling the next middleware ---
        _logger.LogInformation($"Request received: {context.Request.Method} {context.Request.Path}");

        // Call the next middleware in the pipeline
        // If _next(context) is NOT called, the pipeline is short-circuited.
        await _next(context);

        // --- Logic AFTER calling the next middleware (e.g., modifying response) ---
        _logger.LogInformation($"Response sent: {context.Response.StatusCode}");
    }
}

2. Registering the Middleware

After defining your middleware class, you must register it with the application’s request pipeline. This is typically done in your Program.cs file (or Startup.Configure in older ASP.NET Core versions) using app.UseMiddleware<YourMiddleware>().

// In Program.cs (or Startup.Configure):
// Register the custom middleware in the request pipeline
// The order of Use... calls matters!
app.UseMiddleware<MyCustomMiddleware>();

Explanation: Middleware executes in the exact order it is registered. This order is critical. For instance, if you have logging middleware and then authentication middleware, requests will be logged before authentication is checked. Conversely, if authentication is registered first, only authenticated requests will proceed to the logging middleware. Careful ordering allows you to optimize performance (e.g., rate limiting before expensive operations) and enforce security (e.g., authentication before sensitive routes).

3. Dependency Injection within Middleware

Custom middleware can seamlessly leverage ASP.NET Core’s built-in dependency injection (DI) system. Services can be injected into the middleware’s constructor, just like with controllers or other services. This promotes testability, modularity, and adherence to good design principles.

Explanation: By injecting services (like a database context, logging service, or custom business service) into your middleware’s constructor, you can use these services directly within your Invoke or InvokeAsync method. For example, a custom middleware for database logging could inject a database context to record request details, avoiding tight coupling and making the middleware reusable.

Common Use Cases for Custom Middleware

While ASP.NET Core provides many built-in middleware types (e.g., exception handling, routing, static files), custom middleware is invaluable for:

  • Custom Authentication/Authorization: Implementing unique security schemes (e.g., API key validation, custom token handling).
  • Request/Response Modification: Adding or removing headers, compressing responses, or transforming request bodies.
  • Advanced Logging/Auditing: Capturing specific events, request timings, or user activity for audit trails.
  • Rate Limiting: Protecting APIs from abuse by limiting the number of requests from a client.
  • Feature Flags: Dynamically enabling or disabling features based on configuration or user context.