![Title image]()
Middleware is an essential component of the ASP.NET request pipeline. It allows developers to run code during both the request and response lifecycle. In this article, we will explore what middleware is, and how it works, and provide real-world practical use cases with complete code examples.
What is Middleware in .NET?
Middleware in .NET is a software component that processes HTTP requests and responses. It is executed in a pipeline, where each middleware component can perform an action and decide whether to pass the request further or return a response.
Key Characteristics of Middleware
- Executes sequentially in the request pipeline.
- Can modify request and response objects.
- Can terminate request processing if needed.
- Supports dependency injection.
- Used for authentication, logging, rate limiting, and more.
How Middleware Works in .NET?
Middleware components are registered in the Program.cs file using the UseMiddleware<T>() method or inline with app.Use(). The middleware pipeline is executed in the order they are added.
- Receives an HttpContext
- Performs operations (e.g., logging, authentication, etc.)
- Calls the next middleware in the pipeline (or short-circuits the request)
Real-world middleware Use Cases with Code Examples
1. Request Logging Middleware
Use Case: Logging request and response details for debugging and monitoring.
Implementation
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 Invoke(HttpContext context)
{
_logger.LogInformation($"Request: {context.Request.Method} {context.Request.Path}");
await _next(context);
}
}
Register the middleware as shown below.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseMiddleware<LoggingMiddleware>(); //register here!!
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello, Middleware!");
});
app.Run();
2. Custom Authentication Middleware
Use Case: Checking if a request contains a valid API key before processing.
Implementation
public class ApiKeyMiddleware
{
private readonly RequestDelegate _next;
private const string API_KEY = "123456"; // Hardcoded for demonstration
public ApiKeyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
if (!context.Request.Headers.TryGetValue("X-API-KEY", out var extractedApiKey) || extractedApiKey != API_KEY)
{
context.Response.StatusCode = 401; // Unauthorized
await context.Response.WriteAsync("Invalid API Key.");
return;
}
await _next(context);
}
}
Register the middleware
app.UseMiddleware<ApiKeyMiddleware>();
3. Exception Handling Middleware
Use Case: Global error handling to return meaningful error responses.
Implementation
public class ExceptionHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionHandlingMiddleware> _logger;
public ExceptionHandlingMiddleware(RequestDelegate next, ILogger<ExceptionHandlingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex, "An unhandled exception occurred.");
context.Response.StatusCode = 500;
await context.Response.WriteAsync("An unexpected error occurred.");
}
}
}
Register the middleware.
app.UseMiddleware<ExceptionHandlingMiddleware>();
4. Rate Limiting Middleware
Use Case: Preventing abuse by limiting the number of requests from a client in a given time period.
Implementation
public class RateLimitingMiddleware
{
private static readonly Dictionary<string, (int Count, DateTime LastRequest)> _clients = new();
private readonly RequestDelegate _next;
private const int Limit = 5;
private static readonly TimeSpan TimeWindow = TimeSpan.FromMinutes(1);
public RateLimitingMiddleware(RequestDelegate next) => _next = next;
public async Task Invoke(HttpContext context)
{
var clientIP = context.Connection.RemoteIpAddress?.ToString();
if (clientIP == null || !_clients.ContainsKey(clientIP) || DateTime.UtcNow - _clients[clientIP].LastRequest > TimeWindow)
_clients[clientIP] = (1, DateTime.UtcNow);
else if (_clients[clientIP].Count >= Limit)
{
context.Response.StatusCode = 429;
await context.Response.WriteAsync("Rate limit exceeded. Try again later.");
return;
}
else
_clients[clientIP] = (_clients[clientIP].Count + 1, DateTime.UtcNow);
await _next(context);
}
}
Register the middleware
app.UseMiddleware<RateLimitingMiddleware>();
5. Content Compression Middleware
Use Case: Compressing responses to improve performance.
We can use built-in Response Compression or custom Content Compression Middleware.
Implementation. built-in
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddResponseCompression(options =>
{
options.EnableForHttps = true; // Enable compression for HTTPS requests
options.Providers.Add<BrotliCompressionProvider>(); // Brotli compression
options.Providers.Add<GzipCompressionProvider>(); // Gzip compression
});
builder.Services.Configure<GzipCompressionProviderOptions>(options =>
{
options.Level = System.IO.Compression.CompressionLevel.Fastest; // Adjust compression level
});
var app = builder.Build();
app.UseResponseCompression(); // Enable middleware
app.MapGet("/", async context =>
{
var responseText = new string('A', 1000); // Large response for compression testing
await context.Response.WriteAsync(responseText);
});
app.Run();
6. Custom Content Compression Middleware
If you need more control over compression, you can write a custom middleware.
Implementation
public class CustomCompressionMiddleware
{
private readonly RequestDelegate _next;
public CustomCompressionMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var originalBodyStream = context.Response.Body;
using var compressedStream = new MemoryStream();
using var gzipStream = new GZipStream(compressedStream, CompressionMode.Compress, true);
context.Response.Body = gzipStream;
context.Response.Headers.Add("Content-Encoding", "gzip"); // Indicate compression
await _next(context);
gzipStream.Close();
compressedStream.Seek(0, SeekOrigin.Begin);
await compressedStream.CopyToAsync(originalBodyStream);
}
}
6. Response Caching Middleware
Use Case: Improving performance by caching responses.
Implementation
public class ResponseCachingMiddleware
{
private readonly RequestDelegate _next;
private static readonly Dictionary<string, (string Content, DateTime Expiry)> Cache = new();
private static readonly TimeSpan CacheDuration = TimeSpan.FromSeconds(30);
public ResponseCachingMiddleware(RequestDelegate next) => _next = next;
public async Task Invoke(HttpContext context)
{
if (Cache.TryGetValue(context.Request.Path, out var cacheEntry) && DateTime.UtcNow < cacheEntry.Expiry)
{
await context.Response.WriteAsync(cacheEntry.Content);
return;
}
var originalBody = context.Response.Body;
using var memoryStream = new MemoryStream();
context.Response.Body = memoryStream;
await _next(context);
context.Response.Body = originalBody;
var responseContent = Encoding.UTF8.GetString(memoryStream.ToArray());
Cache[context.Request.Path] = (responseContent, DateTime.UtcNow + CacheDuration);
await context.Response.WriteAsync(responseContent);
}
}
7. Request Timing Middleware
Use Case: Measuring the time taken to process a request.
Implementation
public class RequestTimingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestTimingMiddleware> _logger;
public RequestTimingMiddleware(RequestDelegate next, ILogger<RequestTimingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
var stopwatch = Stopwatch.StartNew();
await _next(context);
stopwatch.Stop();
_logger.LogInformation($"Request took {stopwatch.ElapsedMilliseconds} ms");
}
}
Register the middleware
app.UseMiddleware<RequestTimingMiddleware>();
8. Maintenance Mode Middleware
Use Case: Temporarily disabling access to an application during updates.
Implementation
public class MaintenanceMiddleware
{
private readonly RequestDelegate _next;
private readonly bool _isUnderMaintenance = true; // Change to false to disable maintenance mode
public MaintenanceMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
if (_isUnderMaintenance)
{
context.Response.StatusCode = 503;
await context.Response.WriteAsync("The site is under maintenance. Please try again later.");
return;
}
await _next(context);
}
}
Register the middleware
app.UseMiddleware<MaintenanceMiddleware>();
9. Geo-Blocking Middleware
Use Case: Restricting access to specific countries based on IP address.
Implementation
public class GeoBlockingMiddleware
{
private readonly RequestDelegate _next;
private readonly List<string> _blockedCountries = new() { "CN", "RU", "IR" }; // Example blocked countries
public GeoBlockingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
string userCountry = GetUserCountry(context); // Implement this based on an IP lookup service
if (_blockedCountries.Contains(userCountry))
{
context.Response.StatusCode = 403;
await context.Response.WriteAsync("Access denied for your region.");
return;
}
await _next(context);
}
private string GetUserCountry(HttpContext context)
{
// Placeholder logic, use a third-party API or database lookup to determine the country from IP
return "US"; // Assume US for this example
}
}
Register middleware
app.UseMiddleware<GeoBlockingMiddleware>();
10. Request IP Logger Middleware
Use Case: Logging the IP address of every client making a request.
Implementation
public class RequestIpLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestIpLoggingMiddleware> _logger;
public RequestIpLoggingMiddleware(RequestDelegate next, ILogger<RequestIpLoggingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
var ip = context.Connection.RemoteIpAddress?.ToString();
_logger.LogInformation($"Request received from IP: {ip}");
await _next(context);
}
}
Register middleware
app.UseMiddleware<RequestIpLoggingMiddleware>();
11. User Activity Tracking Middleware
public class UserActivityMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<UserActivityMiddleware> _logger;
public UserActivityMiddleware(RequestDelegate next, ILogger<UserActivityMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
var user = context.User.Identity?.Name ?? "Anonymous";
var path = context.Request.Path;
_logger.LogInformation($"User: {user} accessed {path} at {DateTime.UtcNow}");
await _next(context);
}
}
Register middleware
app.UseMiddleware<UserActivityMiddleware>();
12. Dark Mode Cookie Middleware
Use Case: Storing user preferences (like dark mode) in cookies.
public class DarkModeMiddleware
{
private readonly RequestDelegate _next;
public DarkModeMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
if (!context.Request.Cookies.ContainsKey("DarkMode"))
{
context.Response.Cookies.Append("DarkMode", "false");
}
await _next(context);
}
}
Likewise, we can use middleware in .NET for several other use cases.
Conclusion
Middleware is a powerful feature in .NET applications that allows developers to enhance security, optimize performance, and improve user experience. The examples above demonstrate how middleware can be used for logging, authentication, exception handling, rate limiting, rate timing response compression, and caching.
By leveraging middleware effectively, developers can build scalable, maintainable, and high-performance applications. Whether you’re securing APIs, optimizing request handling, or improving efficiency, middleware provides a flexible and extensible solution for modern .NET applications.
Reference