ASP.NET Core Middleware Domination: Ultimate Control Over HTTP Pipelines
Master ASP.NET Core middleware with real-world examples. Learn custom middleware patterns, authentication, logging, error handling & pipeline optimization techniques.
ASPNETCore,Middleware,CustomMiddleware,Authentication,Logging,Pipeline,HTTPPipeline,ErrorHandling,RequestDelegate,APIDevelopment
Module 16: Middleware Domination - Complete Control Over HTTP Pipelines
Table of Contents
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 → Response
Lifecycle 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(); } }
6. Middleware Ordering and Execution
Critical Middleware Order
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // 1. Exception/Error Handling if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); app.UseHsts(); } // 2. HTTPS Redirection app.UseHttpsRedirection(); // 3. Static Files app.UseStaticFiles(); // 4. Routing app.UseRouting(); // 5. Custom Middleware (Authentication, Logging, etc.) app.UseCorrelationId(); app.UseRequestLogging(); app.UseApiKeyAuthentication(); // 6. CORS app.UseCors("AllowAll"); // 7. Authentication & Authorization app.UseAuthentication(); app.UseAuthorization(); // 8. Endpoints app.UseEndpoints(endpoints => { endpoints.MapControllers(); endpoints.MapRazorPages(); }); }
Ordering Impact Example
// Wrong order - Authentication won't work properly app.UseEndpoints(endpoints => { /* ... */ }); app.UseAuthentication(); // This won't be called for endpoint requests // Correct order app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { /* ... */ });
7. Advanced Middleware Patterns
7.1 Conditional Middleware
// Conditional middleware based on request path public class ConditionalMiddleware { private readonly RequestDelegate _next; private readonly string _pathStartsWith; public ConditionalMiddleware(RequestDelegate next, string pathStartsWith) { _next = next; _pathStartsWith = pathStartsWith; } public async Task InvokeAsync(HttpContext context) { if (context.Request.Path.StartsWithSegments(_pathStartsWith)) { // Custom logic for specific paths context.Items["CustomFeature"] = "Enabled"; } await _next(context); } } // Usage with multiple conditions app.MapWhen(context => context.Request.Path.StartsWithSegments("/api"), apiApp => { apiApp.UseMiddleware<ApiSpecificMiddleware>(); apiApp.UseRouting(); apiApp.UseEndpoints(endpoints => { endpoints.MapControllers(); }); });
7.2 Branching Middleware Pipeline
// Branching for different pipeline configurations app.Map("/admin", adminApp => { adminApp.UseMiddleware<AdminAuthenticationMiddleware>(); adminApp.UseRouting(); adminApp.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "admin", pattern: "{controller=Home}/{action=Index}/{id?}"); }); }); app.Map("/api", apiApp => { apiApp.UseMiddleware<ApiKeyMiddleware>(); apiApp.UseRouting(); apiApp.UseEndpoints(endpoints => { endpoints.MapControllers(); }); });
7.3 Middleware with Configuration
// Configurable middleware public class ConfigurableMiddleware { private readonly RequestDelegate _next; private readonly ConfigurableMiddlewareOptions _options; public ConfigurableMiddleware(RequestDelegate next, IOptions<ConfigurableMiddlewareOptions> options) { _next = next; _options = options.Value; } public async Task InvokeAsync(HttpContext context) { if (_options.EnableFeature && context.Request.Path.StartsWithSegments(_options.ApplyToPath)) { // Apply middleware logic await ApplyCustomLogic(context); } await _next(context); } private async Task ApplyCustomLogic(HttpContext context) { // Custom implementation context.Items["CustomFeatureApplied"] = true; } } public class ConfigurableMiddlewareOptions { public bool EnableFeature { get; set; } public string ApplyToPath { get; set; } = "/api"; } // Registration builder.Services.Configure<ConfigurableMiddlewareOptions>(options => { options.EnableFeature = true; options.ApplyToPath = "/secure"; });
8. Error Handling Middleware
Global Exception Handling Middleware
// Comprehensive error handling middleware public class GlobalExceptionHandlerMiddleware { private readonly RequestDelegate _next; private readonly ILogger<GlobalExceptionHandlerMiddleware> _logger; private readonly IWebHostEnvironment _env; public GlobalExceptionHandlerMiddleware( RequestDelegate next, ILogger<GlobalExceptionHandlerMiddleware> logger, IWebHostEnvironment env) { _next = next; _logger = logger; _env = env; } public async Task InvokeAsync(HttpContext context) { try { await _next(context); } catch (Exception ex) { _logger.LogError(ex, "An unhandled exception occurred"); await HandleExceptionAsync(context, ex); } } private async Task HandleExceptionAsync(HttpContext context, Exception exception) { context.Response.ContentType = "application/json"; var errorResponse = new ErrorResponse { Success = false, Error = "An unexpected error occurred" }; switch (exception) { case UnauthorizedAccessException: context.Response.StatusCode = StatusCodes.Status401Unauthorized; errorResponse.Error = "Unauthorized access"; break; case KeyNotFoundException: context.Response.StatusCode = StatusCodes.Status404NotFound; errorResponse.Error = "Resource not found"; break; case ValidationException vex: context.Response.StatusCode = StatusCodes.Status400BadRequest; errorResponse.Error = "Validation failed"; errorResponse.Details = vex.Errors; break; default: context.Response.StatusCode = StatusCodes.Status500InternalServerError; if (_env.IsDevelopment()) { errorResponse.Details = exception.ToString(); } break; } var json = JsonSerializer.Serialize(errorResponse, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); await context.Response.WriteAsync(json); } } public class ErrorResponse { public bool Success { get; set; } public string Error { get; set; } = string.Empty; public object? Details { get; set; } }
Custom Exception Classes
// Custom exception for business logic public class BusinessException : Exception { public string ErrorCode { get; } public BusinessException(string errorCode, string message) : base(message) { ErrorCode = errorCode; } } public class ValidationException : Exception { public Dictionary<string, string[]> Errors { get; } public ValidationException(Dictionary<string, string[]> errors) : base("Validation failed") { Errors = errors; } }
9. Performance Optimization
Response Caching Middleware
// Custom response caching middleware public class ResponseCachingMiddleware { private readonly RequestDelegate _next; private readonly IMemoryCache _cache; private readonly ILogger<ResponseCachingMiddleware> _logger; public ResponseCachingMiddleware( RequestDelegate next, IMemoryCache cache, ILogger<ResponseCachingMiddleware> logger) { _next = next; _cache = cache; _logger = logger; } public async Task InvokeAsync(HttpContext context) { var cacheKey = GenerateCacheKey(context.Request); if (context.Request.Method == "GET" && _cache.TryGetValue(cacheKey, out byte[]? cachedResponse)) { _logger.LogInformation($"Serving cached response for {context.Request.Path}"); context.Response.ContentType = "application/json"; context.Response.ContentLength = cachedResponse?.Length ?? 0; await context.Response.Body.WriteAsync(cachedResponse!); return; } var originalBodyStream = context.Response.Body; using var responseBody = new MemoryStream(); context.Response.Body = responseBody; await _next(context); if (context.Response.StatusCode == 200) { var responseData = responseBody.ToArray(); // Cache the response var cacheOptions = new MemoryCacheEntryOptions() .SetAbsoluteExpiration(TimeSpan.FromMinutes(5)) .SetSlidingExpiration(TimeSpan.FromMinutes(1)); _cache.Set(cacheKey, responseData, cacheOptions); _logger.LogInformation($"Cached response for {context.Request.Path}"); } await responseBody.CopyToAsync(originalBodyStream); } private string GenerateCacheKey(HttpRequest request) { return $"{request.Path}{request.QueryString}"; } }
Performance Monitoring Middleware
// Performance monitoring middleware public class PerformanceMonitoringMiddleware { private readonly RequestDelegate _next; private readonly ILogger<PerformanceMonitoringMiddleware> _logger; public PerformanceMonitoringMiddleware( RequestDelegate next, ILogger<PerformanceMonitoringMiddleware> logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context) { var stopwatch = Stopwatch.StartNew(); var startMemory = GC.GetTotalMemory(false); try { await _next(context); } finally { stopwatch.Stop(); var endMemory = GC.GetTotalMemory(false); var memoryUsed = endMemory - startMemory; _logger.LogInformation( $"Performance - Path: {context.Request.Path}, " + $"Duration: {stopwatch.ElapsedMilliseconds}ms, " + $"Memory: {memoryUsed} bytes, " + $"Status: {context.Response.StatusCode}"); // Log warning for slow requests if (stopwatch.ElapsedMilliseconds > 1000) { _logger.LogWarning( $"Slow request detected: {context.Request.Path} " + $"took {stopwatch.ElapsedMilliseconds}ms"); } } } }
10. Testing Middleware
Unit Testing Middleware
// Unit tests for custom middleware public class RequestLoggingMiddlewareTests { [Fact] public async Task InvokeAsync_LogsRequestInformation() { // Arrange var loggerMock = new Mock<ILogger<RequestLoggingMiddleware>>(); var nextMock = new Mock<RequestDelegate>(); var middleware = new RequestLoggingMiddleware(nextMock.Object, loggerMock.Object); var context = new DefaultHttpContext(); context.Request.Method = "GET"; context.Request.Path = "/api/test"; // Act await middleware.InvokeAsync(context); // Assert nextMock.Verify(next => next(context), Times.Once); // Verify logging calls } [Fact] public async Task InvokeAsync_WhenException_LogsError() { // Arrange var loggerMock = new Mock<ILogger<RequestLoggingMiddleware>>(); var nextMock = new Mock<RequestDelegate>(); nextMock.Setup(next => next(It.IsAny<HttpContext>())) .ThrowsAsync(new Exception("Test exception")); var middleware = new RequestLoggingMiddleware(nextMock.Object, loggerMock.Object); var context = new DefaultHttpContext(); // Act & Assert await Assert.ThrowsAsync<Exception>(() => middleware.InvokeAsync(context)); // Verify error was logged } } // Integration testing public class MiddlewareIntegrationTests : IClassFixture<WebApplicationFactory<Program>> { private readonly WebApplicationFactory<Program> _factory; public MiddlewareIntegrationTests(WebApplicationFactory<Program> factory) { _factory = factory; } [Fact] public async Task CustomMiddleware_AddsCorrelationHeader() { // Arrange var client = _factory.CreateClient(); // Act var response = await client.GetAsync("/api/test"); // Assert response.Headers.Contains("X-Correlation-ID").Should().BeTrue(); } }
11. Best Practices and Alternatives
Middleware Best Practices
11.1 Do's and Don'ts
Do:
Keep middleware focused and single-purpose
Use dependency injection properly
Handle exceptions appropriately
Consider performance implications
Use appropriate logging levels
Don't:
Put business logic in middleware
Block the pipeline unnecessarily
Ignore async/await patterns
Forget to call next() when needed
11.2 Performance Considerations
// Good - Async all the way public async Task InvokeAsync(HttpContext context) { await SomeAsyncOperation(); await _next(context); } // Bad - Mixing sync and async public async Task InvokeAsync(HttpContext context) { SomeSyncOperation(); // Blocks thread await _next(context); }
Alternatives to Custom Middleware
11.3 Action Filters
// Use action filters for controller-specific logic public class LogActionFilter : IActionFilter { private readonly ILogger<LogActionFilter> _logger; public LogActionFilter(ILogger<LogActionFilter> logger) { _logger = logger; } public void OnActionExecuting(ActionExecutingContext context) { _logger.LogInformation($"Executing action: {context.ActionDescriptor.DisplayName}"); } public void OnActionExecuted(ActionExecutedContext context) { _logger.LogInformation($"Executed action: {context.ActionDescriptor.DisplayName}"); } }
11.4 Endpoint Filters
// Endpoint filters for minimal APIs app.MapGet("/api/products", () => { return Results.Ok(products); }) .AddEndpointFilter<LoggingEndpointFilter>(); public class LoggingEndpointFilter : IEndpointFilter { public async ValueTask<object?> InvokeAsync( EndpointFilterInvocationContext context, EndpointFilterDelegate next) { Console.WriteLine($"Before: {context.HttpContext.Request.Path}"); var result = await next(context); Console.WriteLine($"After: {context.HttpContext.Request.Path}"); return result; } }
Complete Real-World Example: E-Commerce Pipeline
// Complete e-commerce application pipeline public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // Exception handling app.UseGlobalExceptionHandler(); // Security app.UseHsts(); app.UseHttpsRedirection(); // Performance app.UseResponseCompression(); app.UseResponseCaching(); // Static files app.UseStaticFiles(); // Routing app.UseRouting(); // Custom middleware app.UseCorrelationId(); app.UseRequestLogging(); app.UsePerformanceMonitoring(); // Authentication & Authorization app.UseAuthentication(); app.UseAuthorization(); // Rate limiting for API app.MapWhen(context => context.Request.Path.StartsWithSegments("/api"), apiApp => { apiApp.UseRateLimiting(); apiApp.UseApiKeyAuthentication(); }); // Endpoints app.UseEndpoints(endpoints => { endpoints.MapControllers(); endpoints.MapRazorPages(); // Health check endpoints.MapHealthChecks("/health"); }); }

0 Comments
thanks for your comments!