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
- Class Definition: Create a class with a constructor that accepts
RequestDelegateand anInvokeorInvokeAsyncmethod. - Extension Method Registration: Register the middleware using
UseMiddleware<T>. - Reflection Analysis: The framework examines the middleware class's constructor and method signatures.
- Delegate Generation: An optimized
Func<RequestDelegate, RequestDelegate>delegate is produced. - Pipeline Construction: The delegate is wrapped into the pipeline during the
Build()process. - 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.