๐ง Introduction
ASP.NET Core is known for its modular pipeline, where middleware components play a critical role in handling HTTP requests and responses. Middleware can perform a variety of tasks such as request logging, authentication, routing, response modification, error handling, and more.
While ASP.NET Core offers built-in middleware like UseRouting
, UseAuthorization
, etc., there are many scenarios where you’ll want to create your own.
In this article, we’ll explore:
๐ง What is Middleware?
Middleware in ASP.NET Core is software that's assembled into the HTTP request pipeline. Each piece of middleware:
-
Processes an incoming request
-
Can optionally pass the request to the next component
-
Optionally processes the response on the way back
Conceptually, it looks like a chain:
Request --> Middleware 1 --> Middleware 2 --> Middleware 3 --> ... --> Response
๐ Why Create Custom Middleware?
You should consider writing custom middleware when:
-
You want reusable, centralized behavior (like logging or metrics)
-
You need to intercept or transform requests or responses
-
Built-in middleware doesn’t meet your specific need
๐ฆ Project Setup
Let’s build a simple ASP.NET Core Web API project to demonstrate.
dotnet new webapi -n CustomMiddlewareDemo
cd CustomMiddlewareDemo
๐จ Example 1: Logging Request Duration
โจ Purpose:
Log how long each HTTP request takes.
๐ Step 1. Create the Middleware
File: Middleware/RequestTimingMiddleware.cs
using Microsoft.AspNetCore.Http;
using System.Diagnostics;
using System.Threading.Tasks;
using System;
namespace CustomMiddlewareDemo.Middleware
{
public class RequestTimingMiddleware
{
private readonly RequestDelegate _next;
public RequestTimingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var stopwatch = Stopwatch.StartNew();
await _next(context); // Call next middleware
stopwatch.Stop();
var elapsedMs = stopwatch.ElapsedMilliseconds;
Console.WriteLine($"โฑ๏ธ [{DateTime.Now}] Request {context.Request.Method} {context.Request.Path} took {elapsedMs}ms");
}
}
}
๐ Step 2. Create an Extension Method
File: Middleware/RequestTimingMiddlewareExtensions.cs
using Microsoft.AspNetCore.Builder;
namespace CustomMiddlewareDemo.Middleware
{
public static class RequestTimingMiddlewareExtensions
{
public static IApplicationBuilder UseRequestTiming(this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestTimingMiddleware>();
}
}
}
๐ Step 3. Register Middleware in Program.cs
using CustomMiddlewareDemo.Middleware;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
var app = builder.Build();
app.UseRequestTiming(); // Our custom middleware
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
๐งช Step 4. Add a Test Endpoint
File: Controllers/TestController.cs
using Microsoft.AspNetCore.Mvc;
using System.Threading;
namespace CustomMiddlewareDemo.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class TestController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
Thread.Sleep(200); // Simulate processing delay
return Ok("Middleware test successful!");
}
}
}
๐ Output Example (Console)
When you visit /api/test
, the console logs:
โฑ๏ธ [7/28/2025 12:34:56 PM] Request GET /api/test took 201ms
๐จ Example 2: Inject a Custom Header into Every Response
โจ Purpose:
Add a custom header to every outgoing HTTP response.
Middleware/CustomHeaderMiddleware.cs
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
namespace CustomMiddlewareDemo.Middleware
{
public class CustomHeaderMiddleware
{
private readonly RequestDelegate _next;
public CustomHeaderMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
context.Response.OnStarting(() =>
{
context.Response.Headers.Add("X-Powered-By", "ASP.NET Core Custom Middleware");
return Task.CompletedTask;
});
await _next(context);
}
}
}
Register it in Program.cs
:
app.UseMiddleware<CustomHeaderMiddleware>();
Now every HTTP response will include:
X-Powered-By: ASP.NET Core Custom Middleware
โ
Best Practices
Practice |
Why it Matters |
Keep middleware small and focused |
Easier to test, reuse, and maintain |
Avoid blocking code (Thread.Sleep ) |
Use await Task.Delay() instead for async code |
Handle exceptions gracefully |
Prevent application crashes and improve logging |
Use dependency injection |
Middleware can use services like logging, DB, etc. |
๐ Summary
Task |
Middleware |
Measure request time |
RequestTimingMiddleware |
Add custom headers |
CustomHeaderMiddleware |
Log request/response |
Custom logging middleware |
Handle errors globally |
Exception-handling middleware |
๐งช Want to Go Further?
-
Try building middleware for JWT validation
-
Create a centralized exception handling middleware
-
Write middleware that reads/writes request bodies