Core API Middleware
Every HTTP request that enters the BookStore API passes through a series of components before it reaches a controller. Every response passes through the same components in reverse order. These components are called middleware. Middleware gives the ability to inspect, modify, short-circuit, or log requests and responses at any point in the pipeline.
What Is Middleware?
Middleware is software assembled into the application pipeline to handle requests and responses. Each middleware component can:
- Perform work before the next component runs
- Pass the request to the next middleware in the pipeline
- Perform work after the next component has completed
- Optionally short-circuit the pipeline and return a response immediately
The Middleware Pipeline
HTTP Request
|
v
┌─────────────────────────────────────────┐
│ Middleware 1: HTTPS Redirection │
│ ↓ (passes request forward) │
│ Middleware 2: Authentication │
│ ↓ │
│ Middleware 3: Authorization │
│ ↓ │
│ Middleware 4: Routing │
│ ↓ │
│ Controller Action (endpoint) │
│ ↑ (response flows back up) │
│ Middleware 4: Routing │
│ ↑ │
│ Middleware 3: Authorization │
│ ↑ │
│ Middleware 2: Authentication │
│ ↑ │
│ Middleware 1: HTTPS Redirection │
└─────────────────────────────────────────┘
|
v
HTTP Response → Client
This is called the request-response pipeline. Each middleware decides whether to call the next one. If it does not, the pipeline is short-circuited and the response goes back to the client immediately.
Built-in Middleware in the BookStore API
// Program.cs – middleware registered in order
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger(); // Serves the swagger.json file
app.UseSwaggerUI(); // Serves the Swagger browser UI
}
app.UseHttpsRedirection(); // Redirects HTTP → HTTPS
app.UseAuthentication(); // Validates JWT tokens (added later)
app.UseAuthorization(); // Checks user permissions
app.MapControllers(); // Routes requests to controllers
| Middleware | What It Does |
|---|---|
UseSwagger() | Generates the OpenAPI specification JSON |
UseSwaggerUI() | Serves the interactive Swagger browser |
UseHttpsRedirection() | Forces all requests to use HTTPS |
UseAuthentication() | Reads and validates the JWT token from headers |
UseAuthorization() | Checks if the authenticated user has permission |
MapControllers() | Routes the request to the matching controller action |
Middleware Order Matters
The order middleware is registered in Program.cs is the order it runs. This matters. Authentication must run before Authorization. Routing must run before controllers are invoked.
// WRONG ORDER (will cause bugs):
app.UseAuthorization(); // tries to check permissions...
app.UseAuthentication(); // ...before user is authenticated!
// CORRECT ORDER:
app.UseAuthentication(); // 1. Identify who the user is
app.UseAuthorization(); // 2. Check what they can do
Writing Custom Middleware
Custom middleware is useful for cross-cutting concerns — tasks that apply to every request. A common use case is request logging: recording what endpoint was called and how long it took.
Option 1: Inline Middleware with app.Use()
// Program.cs
app.Use(async (context, next) =>
{
var startTime = DateTime.UtcNow;
var path = context.Request.Path;
var method = context.Request.Method;
await next(); // Call the next middleware in the pipeline
var duration = (DateTime.UtcNow - startTime).TotalMilliseconds;
Console.WriteLine($"{method} {path} completed in {duration}ms | Status: {context.Response.StatusCode}");
});
Sample output in the console:
GET /api/books completed in 12ms | Status: 200
POST /api/books completed in 45ms | Status: 201
GET /api/books/999 completed in 8ms | Status: 404
Option 2: Class-Based Middleware (Recommended)
For more complex logic, middleware is better written as a separate class:
// Middleware/RequestLoggingMiddleware.cs
namespace BookStoreAPI.Middleware
{
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)
{
var startTime = DateTime.UtcNow;
var method = context.Request.Method;
var path = context.Request.Path;
_logger.LogInformation("→ Incoming: {Method} {Path}", method, path);
await _next(context); // Call next middleware
var duration = (DateTime.UtcNow - startTime).TotalMilliseconds;
_logger.LogInformation("← Completed: {Method} {Path} | {StatusCode} | {Duration}ms",
method, path, context.Response.StatusCode, duration);
}
}
}
Register the custom middleware in Program.cs:
// Program.cs
app.UseMiddleware<RequestLoggingMiddleware>(); // ← add before routing
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
Short-Circuiting the Pipeline
Middleware can stop the pipeline and return a response immediately without calling the next middleware. This is useful for blocking unauthorized or invalid requests early.
app.Use(async (context, next) =>
{
// Block requests without an API key header
if (!context.Request.Headers.ContainsKey("X-Api-Key"))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("API key is missing.");
return; // ← pipeline stops here, next() is NOT called
}
await next(); // ← pipeline continues
});
Terminal Middleware with app.Run()
app.Run() always terminates the pipeline. It never calls the next middleware. This is used at the very end of the pipeline.
app.Run(async (context) =>
{
// This runs last — no next() available
await context.Response.WriteAsync("Request handled.");
});
Middleware Lifecycle Summary
app.Use() → Can call next() OR short-circuit
app.Run() → Always terminates (no next())
app.Map() → Branches the pipeline based on URL path
app.UseMiddleware<T>() → Registers class-based middleware
BookStore API Middleware Order
var app = builder.Build();
// Development only
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
// Custom middleware (runs first for every request)
app.UseMiddleware<RequestLoggingMiddleware>();
// Built-in middleware
app.UseHttpsRedirection();
app.UseAuthentication(); // Added in JWT topic
app.UseAuthorization();
app.MapControllers();
app.Run();
Key Points
- Middleware is software that processes every HTTP request before it reaches the controller and every response before it leaves the server.
- The pipeline is bidirectional — middleware runs on the way in (request) and on the way back out (response).
- Middleware order matters: Authentication must come before Authorization.
- Custom middleware is written using
app.Use()for simple cases or a dedicated class withInvokeAsyncfor complex cases. - Calling
returnwithout callingnext()short-circuits the pipeline and stops further processing.
