🧠 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:

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:

📦 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?