What Is ASP.NET Core Middleware and How to Write It
This article explains the concept of middleware in ASP.NET Core, illustrates how the HTTP request pipeline operates, and provides practical C# code examples for creating and registering custom middleware to process requests and responses.
What Is Middleware in ASP.NET Core?
Middleware is software assembled into an application pipeline to handle incoming HTTP requests and outgoing responses. Each middleware component in ASP.NET Core:
- Decides whether to pass the request to the next component in the pipeline.
- Performs logic both before and after the next component is invoked.
The sequence of middleware components executes in the order they are
defined in Program.cs. When a request enters the
application, it travels through the middleware chain sequentially. Once
a terminating middleware handles the request (or execution finishes),
the response travels back out through the same components in reverse
order.
Common built-in middleware includes authentication, authorization, static file serving, routing, and exception handling.
The Middleware Execution Model
The pipeline relies on a RequestDelegate—a function that
processes an HttpContext. Three primary extension methods
configure middleware in ASP.NET Core:
app.Use(...): Chained middleware that executes code and calls thenextdelegate to continue the pipeline.app.Run(...): Terminal middleware that handles the request and ends the pipeline without calling subsequent components.app.Map(...): Branches the request pipeline based on matching request paths.
Writing Inline Middleware
For simple operations, you can register inline middleware directly in
Program.cs using app.Use:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Use(async (context, next) =>
{
// Code executed before the next middleware
Console.WriteLine($"Request: {context.Request.Method} {context.Request.Path}");
await next(context);
// Code executed after the next middleware
Console.WriteLine($"Response: {context.Response.StatusCode}");
});
app.MapGet("/", () => "Hello World");
app.Run();Writing Class-Based Custom Middleware
For maintainability, complex logic should reside in dedicated middleware classes. Standard middleware adheres to the convention-based pattern:
- A public constructor accepting a
RequestDelegate. - A public method named
InvokeAsyncorInvoketaking anHttpContextand returning aTask.
Step 1: Create the Middleware Class
The following example defines a custom middleware class that calculates and logs the execution duration of an HTTP request:
using System.Diagnostics;
public class PerformanceTrackingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<PerformanceTrackingMiddleware> _logger;
public PerformanceTrackingMiddleware(RequestDelegate next, ILogger<PerformanceTrackingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
var stopwatch = Stopwatch.StartNew();
try
{
// Call the next middleware in the pipeline
await _next(context);
}
finally
{
stopwatch.Stop();
var elapsedMs = stopwatch.ElapsedMilliseconds;
_logger.LogInformation(
"HTTP {Method} {Path} completed in {Elapsed} ms with status code {StatusCode}",
context.Request.Method,
context.Request.Path,
elapsedMs,
context.Response.StatusCode);
}
}
}Step 2: Create an Extension Method
Expose an extension method on IApplicationBuilder to
cleanly integrate the middleware into the pipeline:
public static class PerformanceTrackingMiddlewareExtensions
{
public static IApplicationBuilder UsePerformanceTracking(this IApplicationBuilder builder)
{
return builder.UseMiddleware<PerformanceTrackingMiddleware>();
}
}Step 3: Register the Middleware in the Pipeline
Add the middleware in Program.cs. Ensure placement
reflects the intended execution order relative to routing,
authentication, and endpoints:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Register custom middleware early in the pipeline
app.UsePerformanceTracking();
app.UseRouting();
app.MapGet("/", () => "Pipeline processed successfully.");
app.Run();Key Considerations for Custom Middleware
- Pipeline Order: Position matters. For instance, security middleware should precede endpoint execution, while error-handling middleware should run near the start of the pipeline to catch unhandled exceptions from downstream components.
- Scoped Services: Middleware constructors only
inject Singleton services because middleware instances are instantiated
once at application startup. To use Scoped services (such as an Entity
Framework
DbContext), inject them directly into theInvokeAsyncmethod signature. - Short-Circuiting: Omit calling
await _next(context)to short-circuit the pipeline if validation fails, request limits are exceeded, or unauthorized access is detected.