Skip to content
Shiny Controls v1.0 - The Ultra Control Suite for .NET MAUI & BlazorO...M...G!

Middleware Ordering

By default, middleware executes in the order it was registered with dependency injection. While this works for simple cases, as your middleware pipeline grows you may need explicit control over execution order.

The [MiddlewareOrder] attribute lets you define a numeric order on your middleware classes. Lower values run first (outermost in the pipeline), and higher values run closer to the handler.

Apply [MiddlewareOrder(int)] to your middleware class:

[MiddlewareOrder(-100)]
[MediatorSingleton]
public class ValidationMiddleware<TRequest, TResult> : IRequestMiddleware<TRequest, TResult>
where TRequest : IRequest<TResult>
{
public async Task<TResult> Process(
IMediatorContext context,
RequestHandlerDelegate<TResult> next,
CancellationToken cancellationToken)
{
// Validation runs first (outermost) — before logging, caching, etc.
Validate(context.Message);
return await next();
}
}
[MiddlewareOrder(0)]
[MediatorSingleton]
public class LoggingMiddleware<TRequest, TResult> : IRequestMiddleware<TRequest, TResult>
where TRequest : IRequest<TResult>
{
public async Task<TResult> Process(
IMediatorContext context,
RequestHandlerDelegate<TResult> next,
CancellationToken cancellationToken)
{
// Logging wraps the inner middleware and handler
Log.Debug("Before {Request}", typeof(TRequest).Name);
var result = await next();
Log.Debug("After {Request}", typeof(TRequest).Name);
return result;
}
}
[MiddlewareOrder(100)]
[MediatorSingleton]
public class CachingMiddleware<TRequest, TResult> : IRequestMiddleware<TRequest, TResult>
where TRequest : IRequest<TResult>
{
public async Task<TResult> Process(
IMediatorContext context,
RequestHandlerDelegate<TResult> next,
CancellationToken cancellationToken)
{
// Caching runs last (closest to handler) — cache the final result
return await next();
}
}

With the configuration above, the execution flows as:

Validation → Logging → Caching → Handler → Caching → Logging → Validation

Rule Description
Lower = first A middleware with order -100 runs before order 0 which runs before order 100
Default is 0 Middleware without [MiddlewareOrder] defaults to order 0
Stable sort Middleware with the same order value preserves DI registration order
All types Works for request, command, event, and stream middleware
Open generics Works with both open generic and closed generic middleware

If you don’t use [MiddlewareOrder] at all, everything works exactly as before — middleware executes in DI registration order. The attribute is completely opt-in.

Choose a consistent ordering convention for your project. A common pattern is to use negative values for cross-cutting concerns that should run first (validation, auth) and positive values for middleware that should be close to the handler (caching, offline).