Previous Article: ASP.NET Core Swagger Mastery: Interactive API Documentation Guide (Part-15 of 40)

Table of Contents
Introduction to Middleware
Understanding the Request Pipeline
Built-in Middleware Components
Creating Custom Middleware
Real-World Middleware Examples
Middleware Ordering and Execution
Advanced Middleware Patterns
Error Handling Middleware
Performance Optimization
Testing Middleware
Best Practices and Alternatives
1. Introduction to Middleware
What is Middleware?
Middleware in ASP.NET Core is software components that are assembled into an application pipeline to handle requests and responses. Each component:
Chooses whether to pass the request to the next component
Can perform work before and after the next component
// Simple middleware concept
app.Use(async (context, next) =>
{
// Do work before the next middleware
await next.Invoke();
// Do work after the next middleware
});Real-Life Analogy: Airport Security Check
Think of middleware as airport security checks:
Ticket Verification - Check if you have a valid ticket
Security Screening - Scan luggage and personal items
Passport Control - Verify travel documents
Boarding Gate - Final check before boarding
Each step can either pass you to the next or reject you entirely.
2. Understanding the Request Pipeline
Basic Pipeline Structure
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}Pipeline Flow Visualization
Request → Middleware 1 → Middleware 2 → ... → Middleware N → Endpoint → ResponseLifecycle Example: E-Commerce Request
// Program.cs - Complete pipeline setup
var builder = WebApplication.CreateBuilder(args);
// Add services
builder.Services.AddControllers();
builder.Services.AddAuthentication();
builder.Services.AddAuthorization();
var app = builder.Build();
// Configure pipeline
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseRequestLogging(); // Custom middleware
app.UsePerformanceMonitoring(); // Custom middleware
app.MapControllers();
app.Run();3. Built-in Middleware Components
Essential Built-in Middleware
3.1 Static Files Middleware
// Serve static files like HTML, CSS, JavaScript, images
app.UseStaticFiles();
// Serve files from custom directory
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "MyStaticFiles")),
RequestPath = "/StaticFiles"
});3.2 Routing Middleware
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapGet("/hello", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
});3.3 Authentication & Authorization
app.UseAuthentication();
app.UseAuthorization();3.4 CORS Middleware
// Add CORS service
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAll",
builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
// Use CORS middleware
app.UseCors("AllowAll");4. Creating Custom Middleware
4.1 Inline Middleware
app.Use(async (context, next) =>
{
var startTime = DateTime.UtcNow;
// Call the next middleware
await next();
var endTime = DateTime.UtcNow;
var duration = endTime - startTime;
Console.WriteLine($"Request took: {duration.TotalMilliseconds}ms");
});4.2 Class-Based Middleware
Basic Custom Middleware Class
// Custom logging 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;
_logger.LogInformation($"Starting request: {context.Request.Method} {context.Request.Path}");
try
{
await _next(context);
}
finally
{
var endTime = DateTime.UtcNow;
var duration = endTime - startTime;
_logger.LogInformation(
$"Completed request: {context.Request.Method} {context.Request.Path} " +
$"with status: {context.Response.StatusCode} in {duration.TotalMilliseconds}ms");
}
}
}
// Extension method for easy use
public static class RequestLoggingMiddlewareExtensions
{
public static IApplicationBuilder UseRequestLogging(this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestLoggingMiddleware>();
}
}4.3 Factory-Based Middleware
// Factory-activated middleware
public class TimingMiddleware : IMiddleware
{
private readonly ILogger<TimingMiddleware> _logger;
public TimingMiddleware(ILogger<TimingMiddleware> logger)
{
_logger = logger;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var stopwatch = Stopwatch.StartNew();
_logger.LogInformation($"Starting request: {context.Request.Path}");
await next(context);
stopwatch.Stop();
_logger.LogInformation($"Request {context.Request.Path} completed in {stopwatch.ElapsedMilliseconds}ms");
}
}
// Register in DI container
builder.Services.AddTransient<TimingMiddleware>();5. Real-World Middleware Examples
5.1 API Key Authentication Middleware
// API Key Authentication Middleware
public class ApiKeyMiddleware
{
private readonly RequestDelegate _next;
private readonly IConfiguration _configuration;
private const string API_KEY_HEADER = "X-API-Key";
public ApiKeyMiddleware(RequestDelegate next, IConfiguration configuration)
{
_next = next;
_configuration = configuration;
}
public async Task InvokeAsync(HttpContext context)
{
// Skip API key check for certain paths
if (context.Request.Path.StartsWithSegments("/public"))
{
await _next(context);
return;
}
if (!context.Request.Headers.TryGetValue(API_KEY_HEADER, out var extractedApiKey))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("API Key is missing");
return;
}
var validApiKeys = _configuration.GetSection("ValidApiKeys").Get<List<string>>();
if (!validApiKeys.Contains(extractedApiKey))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Invalid API Key");
return;
}
await _next(context);
}
}
// Extension method
public static class ApiKeyMiddlewareExtensions
{
public static IApplicationBuilder UseApiKeyAuthentication(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ApiKeyMiddleware>();
}
}5.2 Request/Response Logging Middleware
// Detailed request/response logging middleware
public class DetailedLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<DetailedLoggingMiddleware> _logger;
public DetailedLoggingMiddleware(RequestDelegate next, ILogger<DetailedLoggingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
// Log request
await LogRequest(context);
// Copy original response body stream
var originalBodyStream = context.Response.Body;
using var responseBody = new MemoryStream();
context.Response.Body = responseBody;
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex, "An unhandled exception occurred");
throw;
}
finally
{
// Log response
await LogResponse(context, responseBody, originalBodyStream);
}
}
private async Task LogRequest(HttpContext context)
{
context.Request.EnableBuffering();
var request = $"{context.Request.Method} {context.Request.Path}{context.Request.QueryString}";
var headers = string.Join(", ", context.Request.Headers.Select(h => $"{h.Key}: {h.Value}"));
_logger.LogInformation($"Request: {request}");
_logger.LogDebug($"Headers: {headers}");
if (context.Request.ContentLength > 0)
{
using var reader = new StreamReader(context.Request.Body, Encoding.UTF8,
leaveOpen: true);
var body = await reader.ReadToEndAsync();
context.Request.Body.Position = 0;
_logger.LogDebug($"Request Body: {body}");
}
}
private async Task LogResponse(HttpContext context, MemoryStream responseBody, Stream originalBodyStream)
{
responseBody.Position = 0;
var responseText = await new StreamReader(responseBody).ReadToEndAsync();
responseBody.Position = 0;
_logger.LogInformation($"Response: {context.Response.StatusCode}");
_logger.LogDebug($"Response Body: {responseText}");
await responseBody.CopyToAsync(originalBodyStream);
context.Response.Body = originalBodyStream;
}
}5.3 Rate Limiting Middleware
// Simple rate limiting middleware
public class RateLimitingMiddleware
{
private readonly RequestDelegate _next;
private readonly Dictionary<string, List<DateTime>> _requestLog = new();
private readonly object _lockObject = new object();
private readonly int _maxRequests = 100;
private readonly TimeSpan _timeWindow = TimeSpan.FromMinutes(1);
public RateLimitingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var clientIp = context.Connection.RemoteIpAddress?.ToString() ?? "unknown";
if (IsRateLimited(clientIp))
{
context.Response.StatusCode = 429; // Too Many Requests
await context.Response.WriteAsync("Rate limit exceeded. Please try again later.");
return;
}
await _next(context);
}
private bool IsRateLimited(string clientIp)
{
lock (_lockObject)
{
var now = DateTime.UtcNow;
if (!_requestLog.ContainsKey(clientIp))
{
_requestLog[clientIp] = new List<DateTime>();
}
// Remove old requests outside the time window
_requestLog[clientIp].RemoveAll(time => now - time > _timeWindow);
// Check if over limit
if (_requestLog[clientIp].Count >= _maxRequests)
{
return true;
}
// Log this request
_requestLog[clientIp].Add(now);
return false;
}
}
}5.4 Correlation ID Middleware
// Correlation ID middleware for request tracking
public class CorrelationIdMiddleware
{
private readonly RequestDelegate _next;
private const string CORRELATION_ID_HEADER = "X-Correlation-ID";
public CorrelationIdMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context, ILogger<CorrelationIdMiddleware> logger)
{
var correlationId = GetOrCreateCorrelationId(context);
// Add correlation ID to response headers
context.Response.OnStarting(() =>
{
context.Response.Headers[CORRELATION_ID_HEADER] = correlationId;
return Task.CompletedTask;
});
// Add to logger scope
using (logger.BeginScope("{CorrelationId}", correlationId))
{
await _next(context);
}
}
private string GetOrCreateCorrelationId(HttpContext context)
{
if (context.Request.Headers.TryGetValue(CORRELATION_ID_HEADER, out var correlationId))
{
return correlationId!;
}
return Guid.NewGuid().ToString();
}
}
Join the conversation! Your thoughts help the community grow.