Introduction
Middleware plays a crucial role in ASP.NET Core applications by managing how requests are processed. As applications grow, understanding middleware becomes essential to building efficient, maintainable software.
In this article, we’ll explore what middleware is in .NET Core, its role in handling HTTP requests, and the types available. We’ll also cover built-in middleware options like authentication and routing, as well as custom middleware for specific needs.
What Is Middleware in .NET Core?
Middleware is a special class .NET that manages the flow of information in an application. It acts as a bridge between user requests and application responses, running before or after requests to add global features like authentication, logging, compression or error handling to your application in one place.
For instance, logging middleware records details about all requests, such as the URL and timestamp, compression middleware reduces the size of all responses to improve load times and save bandwidth. Middleware enables you to enhance functionality in one place instead of every controller, making applications more organized and scalable.
How Does Middleware Work in .NET Core?
Middleware operates through a pipeline, where each incoming request sequentially passes through various components. Each component can act on the request or response, adding functionality at different processing stages.
Importantly, the order in which you register middleware affects how requests are handled. For example, if logging middleware comes before authentication, every request is logged, even if an unauthenticated request is blocked later. By controlling the order of middleware in your application’s startup configuration, you can manage requests effectively.
Examples of middleware in ASP.NET Core
ASP.NET Core offers a variety of middleware components to handle common tasks, allowing you to streamline your code. Popular examples include authentication, static file serving, routing, and exception handling. The exception handler middleware is frequently used to globally catch exceptions and return responses based on the exception type. A great real-world example is Swagger, by registering it as middleware using the UseSwagger() method, you can access interactive API documentation whenever you request the Swagger endpoint.
How to Create Custom Middleware in .NET Core – Throttling example
Creating custom middleware lets you handle tasks specific to your needs. For example, you might want to implement throttling in your application to protect it from excessive requests originating from a single client.
To create custom middleware, first define a class with an Invoke method that accepts HttpContext. Inside this method, add your custom logic, then call await _next(context); to pass the request to the next middleware. Here’s an example of simple throttling middleware:
public class ThrottlingMiddleware
{
private readonly RequestDelegate _next;
private static readonly ConcurrentDictionary<string, (DateTime Timestamp, int Count)> _requestCounts = new();
private readonly TimeSpan _timeFrame = TimeSpan.FromMinutes(1);
private readonly int _maxRequests = 5;
public ThrottlingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var ipAddress = context.Connection.RemoteIpAddress?.ToString();
if (ipAddress != null)
{
var now = DateTime.UtcNow;
var (timestamp, count) = _requestCounts.GetOrAdd(ipAddress, (now, 0));
if (now - timestamp > _timeFrame)
{
_requestCounts[ipAddress] = (now, 1);
}
else
{
count++;
if (count > _maxRequests)
{
context.Response.StatusCode = StatusCodes.Status429TooManyRequests;
await context.Response.WriteAsync("Too many requests.");
return;
}
_requestCounts[ipAddress] = (timestamp, count);
}
}
await _next(context);
}
}
ThrottlingMiddleware explanation
Here’s a breakdown of what’s happening in key parts of this code:
- Class and Variable Initialization:
- The
ThrottlingMiddleware
class is defined to control the rate of incoming requests. It uses a thread safe dictionary (_requestCounts
) to track the number of requests from each IP address. _timeFrame
and_maxRequests
are set to control the timeframe (1 minute) and limit (5 requests) within which requests are counted.
- The
- Constructor:
- The constructor takes the
RequestDelegate
parameter (next
) to pass along requests when they aren’t being throttled.
- The constructor takes the
- Invoke Method:
- The
Invoke
method runs each time a request is received, and it checks whether the request should be limited.
- The
- IP Address Check:
- It grabs the IP address of the incoming request. If the IP is not available (unlikely in most server environments), the middleware skips further checks.
- Tracking and Limiting Requests:
- For each IP address, the middleware checks or creates an entry in
_requestCounts
with the last request timestamp and request count. - If the request’s timestamp is more than 1 minute (the
_timeFrame
), the request count for the IP is reset to 1 for the new timeframe. - If within the same minute, the count increments, and if it goes above
_maxRequests
, it returns a “429 Too Many Requests” response and blocks the request. That is example how you can skip future processing and generate response from your middleware
- For each IP address, the middleware checks or creates an entry in
- Allowing the Request to Proceed:
- If the IP hasn’t hit the limit, the request is passed along to the next middleware or endpoint in the pipeline using
_next(context)
.
- If the IP hasn’t hit the limit, the request is passed along to the next middleware or endpoint in the pipeline using
How to use Throttling Middleware in .NET application
To use this middleware, register it as first one in the Program.cs under calling var app = builder.Build(); as we did in line 14 in example below:
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseMiddleware<ThrottlingMiddleware>();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
}
}
Now, each request passes through this middleware will check if given Ip Address are not send more then 5 request per minute. You can pull application that are using this .net middleware from our github repository here
Best Practices and Optimizing Middleware
When building middleware, avoid common mistakes that may slow down your application. Performing time-intensive tasks directly within middleware, like complex queries or API calls, can impact request speeds. Instead, keep middleware lightweight, focused on essentials, and include error handling to manage unexpected issues.
In large projects, organize middleware by grouping similar tasks (e.g., logging or authentication) and registering them in a logical order in Startup.cs
. For instance, place logging middleware early to capture all requests, while authentication should come before any secure areas.
Testing middleware is crucial for quality and performance. Unit tests verify middleware functions correctly, even in edge cases like missing request data or unexpected errors. Thorough testing ensures middleware performs reliably under various conditions, making your application stronger and more efficient.
Conclusion
Middleware is essential in ASP.NET Core for managing request and response flows. By adding features like logging, authentication, and error handling, middleware keeps your code organized and efficient. Understanding how to use middleware effectively can make you a more versatile developer, giving you control over how requests are processed.
To dive deeper, check out the official ASP.NET Core Middleware documentation for more examples and advanced concepts.
Experimenting with custom middleware helps solidify your knowledge and allows you to explore its potential in real projects. So, don’t hesitate to try out new ideas, such as adding custom logging or request filtering—it’s a great way to understand middleware and make your applications even more effective.