Introduction to ASP.NET Core Middleware

ASP.NET Core's middleware pipeline is a fundamental concept for handling HTTP requests and responses. Middleware components are arranged in a pipeline, and each component has the opportunity to act on an incoming request before it reaches the application's endpoint, or on an outgoing response before it's sent back to the client.

This modular approach allows for flexible and reusable request processing logic, enabling features like authentication, routing, logging, exception handling, and static file serving to be easily integrated and configured.

The Middleware Pipeline

The core of ASP.NET Core's request processing is the middleware pipeline. When a request arrives, it flows sequentially through each middleware component in the pipeline. Each middleware component receives the HttpContext and a delegate to the next middleware in the pipeline.

  • A middleware can execute logic before the next middleware.
  • A middleware can execute logic after the next middleware.
  • A middleware can short-circuit the pipeline by not calling the next delegate.

The pipeline is configured in the Startup.cs file (or Program.cs in .NET 6+ using minimal APIs) within the Configure method.

Writing Custom Middleware

You can create your own custom middleware components to encapsulate specific request processing logic. There are several ways to write custom middleware:

  1. Using a class with an InvokeAsync method: This is the most common and recommended approach.
  2. Using an Invoke method (older style): Deprecated in favor of InvokeAsync.
  3. Using a factory function: Allows for more complex initialization.

A typical custom middleware class looks like this:

public class MyCustomMiddleware
{
private readonly RequestDelegate _next;

public MyCustomMiddleware(RequestDelegate next)
{
_next = next;
}

public async Task InvokeAsync(HttpContext context)
{
// Logic to execute before the next middleware
Console.WriteLine("Entering MyCustomMiddleware...");

await _next(context); // Call the next middleware in the pipeline

// Logic to execute after the next middleware
Console.WriteLine("Exiting MyCustomMiddleware...");
}
}

To use this middleware, you would register it in your Startup.cs:

app.UseMiddleware<MyCustomMiddleware>();

Using Built-in Middleware

ASP.NET Core provides a rich set of built-in middleware for common web development tasks. Here are a few examples:

  • UseStaticFiles(): Serves static files (HTML, CSS, JS, images).
  • UseRouting(): Implements the routing system.
  • UseAuthentication(): Handles authentication.
  • UseAuthorization(): Handles authorization.
  • UseDeveloperExceptionPage(): Shows detailed error pages in development.
  • UseHttpsRedirection(): Redirects HTTP requests to HTTPS.
  • UseCors(): Configures Cross-Origin Resource Sharing (CORS).

These are typically added using extension methods on the IApplicationBuilder interface.

Order Matters

The order in which middleware components are added to the pipeline is crucial. Middleware is executed in the order it's added.

  • Middleware added earlier in the pipeline is executed first for incoming requests.
  • Middleware added later in the pipeline is executed first for outgoing responses (after the pipeline unwinds).

For example, UseRouting() must generally come before middleware that inspects route data, like endpoint selection middleware or authorization middleware. UseAuthentication() should typically be called before UseAuthorization().

The `next` Delegate

The RequestDelegate is a function that accepts an HttpContext and returns a Task. When your middleware calls await _next(context);, it's passing the control flow to the next middleware in the pipeline. If your middleware doesn't call the next delegate, it effectively terminates the pipeline for that request.

Request Delegates

A Request Delegate is the signature for a delegate that handles an HTTP request. In ASP.NET Core, it's represented by the RequestDelegate type, which is a Func<HttpContext, Task>. The entire pipeline can be thought of as a chain of Request Delegates.

Putting It All Together: A Simple Example

Consider this configuration in Startup.cs (or Program.cs):

// In Startup.Configure(IApplicationBuilder app, IWebHostEnvironment env)
// or in Program.cs with minimal APIs

if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication(); // If authentication is used
app.UseAuthorization(); // If authorization is used

app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
});

// Custom middleware added last for demonstration
app.UseMiddleware<MyCustomMiddleware>();

In this example:

  • If in development, the exception page is shown first.
  • HTTPS redirection happens next.
  • Static files are served.
  • Routing is initialized.
  • Authentication and Authorization are checked.
  • Endpoints are mapped.
  • Finally, MyCustomMiddleware runs. If it calls _next, the request continues to the mapped endpoint.