Masterclass: MediatR Pipeline Behaviors & Validation Architecture
Architectural Purpose
Pipeline behaviors act as cross-cutting interceptors (similar to ASP.NET Core middleware, but specifically scoped to MediatR request execution flows) enabling logging, validation, and transaction management without cluttering handlers.
1. Technical Q&A: Pipeline Behaviors, Middleware & C# 12
Q1: Why did we need to wire up the validator and pipeline behavior? Why won’t it run without wiring?
Answer:
- Dependency Injection (DI) Container ignorance: The .NET runtime and MediatR do not automatically scan your entire hard drive for classes.
- Explicit Registration:
AddValidatorsFromAssemblytells the DI container “scan this project for anything inheriting from AbstractValidator and register it,” whileAddOpenBehaviortells MediatR: “wrap all requests in this behavior class before reaching the handler.” Without these configuration hooks, the classes remain isolated text files that the runtime never instantiates.
Q2: Does a pipeline behavior mean it has become a middleware? Why do we see await next() without a explicit delegate property?
Answer:
- Conceptual Middleware: Yes! It is effectively middleware for your CQRS pipeline. Just as ASP.NET Core middleware intercepts HTTP requests coming into the web server, a MediatR
IPipelineBehaviorintercepts requests travelling from your API controller/endpoint down to your Command Handler. - Where is the delegate? The delegate is passed as a parameter into the
Handlemethod:RequestHandlerDelegate<TResponse> next. In C#, aRequestHandlerDelegate<TResponse>is a wrapper function delegate representing the next step in the pipeline (either another behavior or the final handler itself). Invokingawait next()executes that next step and awaits its return value.
Q3: Can you explain the class signature and C# 12 primary constructor syntax used here?
Signature:
public class ValidationBehavior<TRequest, TResponse>(IEnumerable<IValidator<TRequest>> validators)
: IPipelineBehavior<TRequest, TResponse>
where TRequest : notnull- Primary Constructor (
(IEnumerable<IValidator<TRequest>> validators)): Introduced in C# 12, this allows you to declare constructor parameters directly on the class header. Behind the scenes, the compiler automatically generates a private backing field and assigns the parameter so you can usevalidatorsanywhere inside the class without boilerplate field declarations. - Generic Constraints (
where TRequest : notnull): RestrictsTRequestso that it cannot be a nullable reference type, enforcing safe handling throughout the pipeline.
2. Essential Concepts Cheat Sheet
Pipeline Behavior Flow
- Incoming Request: Hits the pipeline behavior first.
- Validation Check: Runs all registered
IValidator<TRequest>instances concurrently (Task.WhenAll).- Fail-Fast Guard: If errors exist, throws
ValidationExceptionimmediately, aborting execution.- Delegation: If validation passes, calls
await next()to proceed to the Command Handler.