How Custom Middleware Enters the ASP.NET Core Request Pipeline Execution Queue

How Custom Middleware Enters the ASP.NET Core Request Pipeline Execution Queue

1. Standard Implementation of a Custom Middleware Class

public class RequestAuditMiddleware
{
    private readonly RequestDelegate _nextDelegate;
    
    // 1. Constructor must accept RequestDelegate as a parameter
    public RequestAuditMiddleware(RequestDelegate nextDelegate)
    {
        _nextDelegate = nextDelegate;
    }

    // 2. Must expose a public method named Invoke or InvokeAsync
    public async Task InvokeAsync(HttpContext httpContext)
    {
        // Pre-processing logic
        var watch = System.Diagnostics.Stopwatch.StartNew();
        
        // Call the next middleware in the pipeline
        await _nextDelegate(httpContext);       

        // Post-processing logic
        watch.Stop();
        Console.WriteLine($"Request to {httpContext.Request.Path} took {watch.ElapsedMilliseconds}ms");
    }
}

2. Registering Middleware Using an Extansion Method

public static class RequestAuditMiddlewareExtensions
{
    public static IApplicationBuilder UseRequestAudit(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<RequestAuditMiddleware>();
    }
}

3. Adding the Middleware to the Pipeline

public void Configure(IApplicationBuilder builder, IWebHostEnvironment env)
{
    builder.UseRequestAudit();  // Custom middleware registration
    builder.UseHttpsRedirection();
    builder.UseStaticFiles();
    builder.UseRouting();
    builder.UseAuthorization();
    builder.MapControllers();
}

4. Detailed Transformation Process

The following section explains how UseMiddleware<RequestAuditMiddleware>() converts a class into a pipeline component.

Step 1: The UseMiddleware<T> Extension Method

// Located in Microsoft.AspNetCore.Builder.UseMiddlewareExtensions
public static class UseMiddlewareExtensions
{
    public static IApplicationBuilder UseMiddleware<TMiddleware>(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware(typeof(TMiddleware));
    }

    public static IApplicationBuilder UseMiddleware(this IApplicationBuilder builder, Type middlewareType)
    {
        // Create the middleware factory
        return builder.Use(next =>
        {
            // This is the heart of the transformation.
            // A delegate is created that instantiates the middleware and invokes it on each request.
            var componentFactory = (IMiddlewareFactory)builder.ApplicationServices
                .GetService(typeof(IMiddlewareFactory));

            return async context =>
            {
                // This delegate executes on every request.
                var component = componentFactory.Create(middlewareType);
                try
                {
                    // Calls the middleware's Invoke or InvokeAsync method.
                    await component.InvokeAsync(context, next);
                }
                finally
                {
                    componentFactory.Release(component);
                }
            };
        });
    }
}

Step 2: The Delegate Generated at Runtime

In practice, ASP.NET Core uses a highly efficient code generation mechanism. The simplified equivalent delegate looks like this:

// Runtime-generated delegate (simplified):
Func<RequestDelegate, RequestDelegate> generatedMiddleware = next =>
{
    return async context =>
    {
        // Create an instance of the custom middleware
        var middleware = new RequestAuditMiddleware(next);
        
        // Execute the InvokeAsync method
        await middleware.InvokeAsync(context);
    };
};

Step 3: Pipeline Construction

// The terminal endpoint of the pipeline
RequestDelegate terminalEndpoint = async context => 
{
    context.Response.StatusCode = 404; // Default 404 response
};

// Apply the custom middleware to the terminal endpoint
var registeredMiddleware = generatedMiddleware(terminalEndpoint);

// The final resulting delegate
RequestDelegate finalPipeline = async context =>
{
    var middlewareInstance = new RequestAuditMiddleware(terminalEndpoint);
    
    await middlewareInstance.InvokeAsync(context);
};

Complete Transformation Flow

Custom Middleware Class
    ↓
UseMiddleware<T>() Extension Method
    ↓
Reflection-based analysis of constructor and methods
    ↓
Generation of an optimized delegate (Func<RequestDelegate, RequestDelegate>)
    ↓
Registration in the IApplicationBuilder middleware collection
    ↓
Wrpped into a final RequestDelegate during pipeline build
    ↓
Execution of the generated delegate when a request arrives

Supporting Multiple Middleware Signatures

ASP.NET Core supports various method signatures, and the converter handles them inteelligently.

Pattern 1: Standard InvokeAsync Method

public async Task InvokeAsync(HttpContext context)
{
    await _nextDelegate(context);
}

Pattern 2: InvokeAsync with Injected Services

public async Task InvokeAsync(HttpContext context, IInventoryService inventory)
{
    // Dependencies are automatically resolved from the DI container
    await _nextDelegate(context);
}

Pattern 3: Synchronous Invoke Method

public void Invoke(HttpContext context)
{
    // Synchronous processing
    _nextDelegate(context).Wait();
}

Real-World Complex Middleware Example

public class ConcurrencyThrottleMiddleware
{
    private readonly RequestDelegate _nextDelegate;
    private readonly IMemoryCache _localCache;
    private readonly ThrottleConfiguration _config;

    // Constructor injection
    public ConcurrencyThrottleMiddleware(RequestDelegate nextDelegate, IMemoryCache localCache, IOptions<ThrottleConfiguration> options)
    {
        _nextDelegate = nextDelegate;
        _localCache = localCache;
        _config = options.Value;
    }

    // Method parameter injection
    public async Task InvokeAsync(HttpContext context, ILogger<ConcurrencyThrottleMiddleware> logger)
    {
        var clientIp = context.Connection.RemoteIpAddress?.ToString();
        var cacheKey = $"throttle_{clientIp}";
        
        // Throttle logic
        if (_localCache.TryGetValue(cacheKey, out int requestCounter) && requestCounter >= _config.MaxRequests)
        {
            logger.LogWarning("Client {IP} exceeded throttle limit", clientIp);
            context.Response.StatusCode = 429;
            await context.Response.WriteAsync("Rate limit exceeded");
            return;
        }
        
        // Update counter
        _localCache.Set(cacheKey, requestCounter + 1, TimeSpan.FromMinutes(1));
        
        await _nextDelegate(context);
    }
}

public class ThrottleConfiguration
{
    public int MaxRequests { get; set; } = 100;
}

Automatic Dependency Injection Handling

During the transformation, the dependency injection container automatically resolves dependencies.

// The converter analyzes constructor and method parameters
public async Task InvokeAsync(
    HttpContext context, 
    ILogger<ConcurrencyThrottleMiddleware> logger,   // Injected via method parameter
    IMemoryCache localCache,                          // Injected via method parameter  
    IOptions<ThrottleConfiguration> options)         // Injected via method parameter
{
    // All these parameters are resolved automatically from the DI container
}

Summary: Key Steps in the Transformation Flow

  1. Class Definition: Create a class with a constructor that accepts RequestDelegate and an Invoke or InvokeAsync method.
  2. Extension Method Registration: Register the middleware using UseMiddleware<T>.
  3. Reflection Analysis: The framework examines the middleware class's constructor and method signatures.
  4. Delegate Generation: An optimized Func<RequestDelegate, RequestDelegate> delegate is produced.
  5. Pipeline Construction: The delegate is wrapped into the pipeline during the Build() process.
  6. Request Execution: A middleware instance is created on each request and its method is invoked.

Benefits of this design:

  • Type Safety: Middleware signatures are validated at compile time.
  • Dependency Injection: Service lifetimes are handled automatically.
  • Performance Optimization: Code generation replaces pure reflection.
  • Flexibility: Multiple method signatures and injection patterns are supported.

Tags: ASP.NET Core middleware Pipeline Dependency Injection C#

Posted on Wed, 20 May 2026 21:14:27 +0000 by Natty_Dreadlock