In modern web application development, efficiently managing HTTP requests is crucial for performance, security, and maintenance. In .NET, middlewares are essential for configuring the request processing pipeline, allowing developers to handle requests and responses in a modular and extendable way. This article explores what middlewares are, how to implement them in .NET, and best practices for their effective use.
What is a Middleware?
Think of your web application like a restaurant kitchen. When a customer places an order (HTTP request), the order goes through various stations (middlewares) in the kitchen, each with its specific task, before reaching the customer's table (HTTP response).
A middleware acts as one of these stations. Each middleware can:
- Process incoming HTTP requests.
- Execute logic before and/or after passing the request to the next middleware.
- Terminate the request or pass it through the chain of middlewares.
This design allows developers to add cross-cutting features like authentication, logging, error handling, and more in a modular way.
Types of Middlewares
In ASP.NET Core, middlewares can be categorized into three main types:
- Third-Party Middlewares: Provided by external libraries, such as OAuth authentication.
- Built-in Middlewares: Included in ASP.NET Core, such as error handling, authentication, and authorisation.
- Custom Middlewares: Created by users for specific requirements.
Project Structure in .NET
In .NET, the entry point of an application is in the Program
class, where the middleware pipeline and necessary services are configured.
Example Project
Start by creating a new ASP.NET Core project:
dotnet new web -n MiddlewareExample
The basic project structure will include a Program.cs
class.
Program.cs
Class
In the Program
class, you configure middlewares and services. Here’s a complete example:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Threading.Tasks;
var builder = WebApplication.CreateBuilder(args);
// Configure services
builder.Services.AddControllers();
var app = builder.Build();
// Configure the middleware pipeline
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
// Custom middleware
app.UseMiddleware<LoggingMiddleware>();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.Run();
// Custom middleware
public class LoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<LoggingMiddleware> _logger;
public LoggingMiddleware(RequestDelegate next, ILogger<LoggingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
_logger.LogInformation($"Incoming request: {context.Request.Method} {context.Request.Path}");
await _next(context);
_logger.LogInformation($"Outgoing response: {context.Response.StatusCode}");
}
}
Built-in Middlewares
ASP.NET Core comes with several built-in middlewares commonly used in applications.
Error Handling
The error handling middleware captures unhandled exceptions and provides a user-friendly error response:
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
Authentication and Authorisation
To add authentication and authorisation:
app.UseAuthentication();
app.UseAuthorization();
Response Compression Middleware
To add response compression:
builder.Services.AddResponseCompression();
app.UseResponseCompression();
Creating a Custom Middleware
Custom middleware follows a specific structure. Here’s an example of a middleware that measures the processing time of a request:
Request Timing Middleware
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 InvokeAsync(HttpContext context)
{
var startTime = DateTime.UtcNow;
context.Response.OnStarting(() =>
{
var duration = DateTime.UtcNow - startTime;
context.Response.Headers["X-Processing-Time-Milliseconds"] = duration.TotalMilliseconds.ToString();
return Task.CompletedTask;
});
await _next(context);
}
}
To register this middleware in the pipeline:
app.UseMiddleware<RequestTimingMiddleware>();
Best Practices
Order of Middlewares: The sequence in which middlewares are registered is crucial. Ensure authentication, authorisation, and error handling middlewares are positioned correctly.
Avoid Heavy Logic: Middlewares should be lightweight and fast. Avoid performing expensive operations within them.
Reuse: Utilize built-in and third-party middlewares whenever possible to avoid reinventing the wheel.
Exception Handling: Handle exceptions within middlewares to prevent information leaks and maintain consistent responses.
Proper Logging: Implement effective logging to monitor middleware behaviour and aid debugging.
Conclusion
Middlewares are vital components in ASP.NET Core that enable modular handling and processing of HTTP requests. With .NET, configuring and using middlewares has been streamlined and centralised in the Program
class.