Skip to main content

.NET Middleware Pipeline

The ASP.NET Core middleware pipeline is a fundamental concept that controls how HTTP requests are processed in your application. Understanding how it works is crucial for building robust APIs.

What is Middleware?

Middleware is software that's assembled into an application pipeline to handle requests and responses. Each component:

  • Chooses whether to pass the request to the next component in the pipeline
  • Can perform work before and after the next component in the pipeline

Think of middleware as a series of layers that each HTTP request must pass through, with each layer having the opportunity to process the request on the way in and the response on the way out.

The Pipeline Concept

Request → [Middleware 1] → [Middleware 2] → [Middleware 3] → Endpoint
↓ ↓ ↓ ↓
Response ← [Middleware 1] ← [Middleware 2] ← [Middleware 3] ← Endpoint

Each middleware component can:

  1. Pass the request to the next middleware using next()
  2. Short-circuit the pipeline by not calling next()
  3. Process before calling next() (on the request path)
  4. Process after calling next() (on the response path)

How Middleware is Configured

In ASP.NET Core, middleware is configured in the Program.cs file using the WebApplicationBuilder. Here's a typical setup:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container
builder.Services.AddControllers();

var app = builder.Build();

// Configure the HTTP request pipeline
app.UseHttpsRedirection(); // Middleware 1
app.UseAuthentication(); // Middleware 2
app.UseAuthorization(); // Middleware 3
app.MapControllers(); // Endpoint routing

app.Run();

Order Matters!

The order in which you add middleware is critical. Middleware executes in the order it's added. For example:

// ✅ Correct order
app.UseAuthentication(); // First, identify the user
app.UseAuthorization(); // Then, check permissions

// ❌ Wrong order
app.UseAuthorization(); // Can't authorize without knowing who the user is!
app.UseAuthentication();

Common Middleware Components

1. Exception Handling Middleware

Should be one of the first middleware components to catch exceptions from later middleware.

if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/error");
}

2. HTTPS Redirection

Redirects HTTP requests to HTTPS.

app.UseHttpsRedirection();

3. Static Files

Serves static files like images, CSS, and JavaScript.

app.UseStaticFiles();

4. Routing

Matches requests to endpoints.

app.UseRouting();

5. CORS

Enables Cross-Origin Resource Sharing.

app.UseCors("MyPolicy");

6. Authentication & Authorization

Authenticates users and checks permissions.

app.UseAuthentication();
app.UseAuthorization();

7. Endpoint Routing

Maps controllers, Minimal APIs, or other endpoints.

app.MapControllers();
// or
app.MapGet("/hello", () => "Hello World!");

Creating Custom Middleware

You can create your own middleware in three ways:

1. Inline Middleware (Use)

app.Use(async (context, next) =>
{
// Do work before the next middleware
Console.WriteLine("Before next middleware");

await next(context);

// Do work after the next middleware
Console.WriteLine("After next middleware");
});

2. Terminal Middleware (Run)

This doesn't call next() - it terminates the pipeline.

app.Run(async context =>
{
await context.Response.WriteAsync("Pipeline ends here!");
});

3. Middleware Class

For more complex middleware, create a dedicated class:

public class RequestLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestLoggingMiddleware> _logger;

public RequestLoggingMiddleware(
RequestDelegate next,
ILogger<RequestLoggingMiddleware> logger)
{
_next = next;
_logger = logger;
}

public async Task InvokeAsync(HttpContext context)
{
// Log the incoming request
_logger.LogInformation(
"Request: {Method} {Path}",
context.Request.Method,
context.Request.Path);

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

// Log the response
_logger.LogInformation(
"Response: {StatusCode}",
context.Response.StatusCode);
}
}

// Extension method for easy registration
public static class RequestLoggingMiddlewareExtensions
{
public static IApplicationBuilder UseRequestLogging(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestLoggingMiddleware>();
}
}

// Usage in Program.cs
app.UseRequestLogging();

Here's a recommended order for common middleware components:

  1. Exception handling (UseDeveloperExceptionPage or UseExceptionHandler)
  2. HTTPS Redirection (UseHttpsRedirection)
  3. Static Files (UseStaticFiles)
  4. Routing (UseRouting)
  5. CORS (UseCors)
  6. Authentication (UseAuthentication)
  7. Authorization (UseAuthorization)
  8. Custom middleware (your business logic)
  9. Endpoints (MapControllers, MapGet, etc.)

Key Takeaways

  • Middleware forms a pipeline through which every HTTP request flows
  • Each middleware can process the request going in and the response coming out
  • Order matters - middleware executes in the order it's registered
  • Middleware can short-circuit the pipeline by not calling next()
  • You can create custom middleware for cross-cutting concerns like logging, timing, etc.
  • Understanding the pipeline helps you debug issues and optimize performance

Further Reading