Validation & MediatR Pipeline
Relevant source files
The following files were used as context for generating this wiki page:
- BuildingBlocks/BuildingBlocks.Application/Behaviors/ValidationBehavior.cs
- BuildingBlocks/BuildingBlocks.Application/Behaviors/LoggingBehavior.cs
- BuildingBlocks/BuildingBlocks.Tests/TransactionBehaviorTests.cs
- BuildingBlocks/BuildingBlocks.Application/Validators/UserValidator.cs
- BuildingBlocks/BuildingBlocks.WebApplications/Filters/ExceptionToJsonFilter.cs
- BuildingBlocks/BuildingBlocks.WebApplications/Filters/ValidationErrorWrapper.cs
- Core/Scheduling/Scheduling.Application/Patients/Commands/CreatePatientCommand.cs
The Validation and MediatR Pipeline system provides a structured, cross-cutting approach to handling request processing in the application. It utilizes MediatR IPipelineBehavior to intercept commands and queries, ensuring that logging, validation, and transaction management are applied consistently before a request reaches its specific handler.
This architecture decouples business logic from infrastructure concerns like authorization checks, input validation, and database transaction atomicity. It ensures that any request entering the system is properly logged, validated against domain rules, and executed within a managed unit of work.
Sources: claude.md:57-63, BuildingBlocks.Application/Behaviors/ValidationBehavior.cs:7-12
Pipeline Architecture
The pipeline consists of several sequential behaviors that wrap the execution of a MediatR request. Each behavior performs a specific task and then either passes control to the next behavior in the chain or halts execution by throwing an exception (e.g., if validation fails).
Execution Flow
The following sequence diagram illustrates how a request moves through the pipeline:
Pipeline Components
| Component | Responsibility |
|---|---|
| LoggingBehavior | Records the start, successful completion, and errors of every request. It suppresses logging for ValidationException as these are considered expected client errors. |
| ValidationBehavior | Automatically discovers and executes all IValidator<T> implementations for the current request. Throws a ValidationException if failures are detected. |
| TransactionBehavior | Manages IUnitOfWork transactions. For Commands, it opens a transaction and commits on success (including validation failures during execution). Queries typically skip transaction overhead. |
| UserValidator | A specialized base class for FluentValidation that integrates role-based authorization directly into the validation pipeline. |
Sources: BuildingBlocks.Application/Behaviors/LoggingBehavior.cs:7-12, BuildingBlocks.Application/Behaviors/ValidationBehavior.cs:7-12, BuildingBlocks.Tests/TransactionBehaviorTests.cs:30-45, BuildingBlocks.Application/Validators/UserValidator.cs:12-18
Validation Strategy
Validation is implemented using FluentValidation. The pipeline automatically injects all registered validators for a given request type and executes them asynchronously before the handler is invoked.
Role-Based Authorization in Validation
The UserValidator<T> base class allows developers to define role requirements as part of the validation logic. This ensures that authorization is checked early in the request lifecycle.
The system supports complex role logic (AND/OR) via nested collections:
[["Admin"], ["Doctor"]]: User must be an Admin OR a Doctor.[["Doctor", "Nurse"]]: User must be BOTH a Doctor AND a Nurse.
Sources: BuildingBlocks.Application/Validators/UserValidator.cs:25-50, BuildingBlocks.Application/Validators/UserValidator.cs:64-75
Implementation Example
The following snippet demonstrates a command with both role-based and field-level validation:
internal class CreatePatientCommandValidator : UserValidator<CreatePatientCommand>
{
public CreatePatientCommandValidator(ICurrentUser currentUser, IValidator<CreatePatientRequest> reqValidator)
: base(currentUser, new[] { AppRoles.Nurse }, new[] { AppRoles.Doctor }, new[] { AppRoles.Admin })
{
RuleFor(c => c.Patient).SetValidator(reqValidator);
}
}
Sources: Scheduling.Application/Patients/Commands/CreatePatientCommand.cs:39-47
Global Exception & Validation Handling
The application uses an ExceptionToJsonFilter and ValidationErrorWrapper to transform pipeline exceptions into standardized JSON responses for API clients.
HTTP Status Mapping
The ValidationErrorWrapper determines the appropriate HTTP status code based on the nature of the validation failure.
| Scenario | HTTP Status | Logic |
|---|---|---|
| Role Gate Failure | 403 Forbidden | Occurs if a check in UserValidator<T> fails. |
| Standard Validation | 400 Bad Request | Default for input validation failures. |
| Numeric Error Code | 100-599 | If a single error has a numeric code, it is used as the status. |
| Unhandled Error | 500 Internal Error | Handled by HandleUnexpectedException in the filter. |
Sources: BuildingBlocks.WebApplications/Filters/ValidationErrorWrapper.cs:69-80, BuildingBlocks.WebApplications/Filters/ExceptionToJsonFilter.cs:65-75
Error Serialization
Validation errors are categorized by severity (Error or Warning) and returned with custom error codes. Codes are prefixed (e.g., ERR_ or WRN_) to assist client-side handling.
Sources: BuildingBlocks.WebApplications/Filters/ValidationErrorWrapper.cs:28-55
Summary
The Validation & MediatR Pipeline establishes a robust "clean architecture" pattern where technical concerns are abstracted away from business logic. By combining MediatR behaviors with FluentValidation, the system ensures that every command is authenticated, validated, logged, and executed within a safe transaction boundary, while providing consistent error reporting to the front-end via global filters.