Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ dotnet new vsa -n <YourProjectName>
- ✅ Using `Postgres` On Top of EfCore
- ✅ Using `OpenTelemetry` for collection `logs`, `metrics` and `tracings`
- ✅ Using `OpenTelemetry Collector` to receive, process, and export telemetry data to various backends, including Jaeger and Tempo for tracing, Loki and Kibana for logs, and Prometheus for metrics
- ✅ Using `Fluent Validation` and a [Validation Pipeline Behaviour](./src/BuildingBlocks/BuildingBlocks.Validation/RequestValidationBehavior.cs) on top of MediatR
- ✅ Using `Fluent Validation` and a [Validation Pipeline Behaviour](./src/Shared/Validation/README.md) on top of MediatR
- ✅ Using different levels of tests like `Unit Tests`, `Integration Tests`, `Contract Test` and `End-To-End Tests`
- ✅ Using [Microsoft Tye](https://github.com/dotnet/tye) and `Pm2` for running the application
- ✅ Using docker and `docker-compose` for deployment
Expand Down
47 changes: 47 additions & 0 deletions src/Shared/Validation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Validation Behaviors

## ✨ Summary
This is FluentValidation + MediatR pipeline integration, cleanly implemented to:

- Auto-validate requests before handling them

- Log everything for observability

- Work with both regular and streaming MediatR requests

- Keep your handlers minimal and focused (no validation clutter inside handlers)

## 📦 Context
This folder contains `MediatR` pipeline behaviors for automatic request validation using `FluentValidation`.
They are cross-cutting concerns that run before and after handling a request.
Here, the concern is validation — validating requests automatically before executing handlers.

## What is it? 🧐

These behaviors automatically validate requests before they reach their handlers. This ensures that:

- Handlers remain clean and focused (no manual validation code)
- Validation logic is centralized and consistent
- Invalid requests short-circuit the pipeline with clear error messages

## How does it work?

- If a `FluentValidation` validator exists for a request, it will be invoked automatically.
- If validation passes, the request handler executes normally.
- If validation fails, an exception is thrown (handled globally).

## Usage

1. Ensure validators are registered in DI (`FluentValidation`)
2. Register the pipeline behavior in `MediatR`

```csharp
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(RequestValidationBehavior<,>));
services.AddScoped(typeof(IStreamPipelineBehavior<,>), typeof(StreamRequestValidationBehavior<,>));
```

## Related
- [FluentValidation](https://docs.fluentvalidation.net/en/latest/)
- [MediatR Pipeline Behaviors](https://github.com/jbogard/MediatR/wiki/Behaviors)
- [Request Validation Behaviour](RequestValidationBehavior.cs)

28 changes: 28 additions & 0 deletions src/Shared/Validation/RequestValidationBehavior.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,27 @@

namespace Shared.Validation;

/// <summary>
/// A MediatR pipeline behavior that automatically validates a request using FluentValidation
/// before invoking its handler.
/// </summary>
/// <typeparam name="TRequest">The type of the request message.</typeparam>
/// <typeparam name="TResponse">The type of the response message returned by the handler.</typeparam>
public class RequestValidationBehavior<TRequest, TResponse>(
IServiceProvider serviceProvider,
ILogger<RequestValidationBehavior<TRequest, TResponse>> logger
) : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
where TResponse : class
{
/// <summary>
/// Handles the incoming request by validating it (if a validator is registered)
/// and then invoking the next handler in the pipeline.
/// </summary>
/// <param name="message">The request message to be handled.</param>
/// <param name="cancellationToken">A token to observe while waiting for the task to complete.</param>
/// <param name="next">The delegate representing the next step in the pipeline.</param>
/// <returns>The response returned by the request handler.</returns>
public async ValueTask<TResponse> Handle(
TRequest message,
CancellationToken cancellationToken,
Expand Down Expand Up @@ -46,6 +60,12 @@ MessageHandlerDelegate<TRequest, TResponse> next
}
}

/// <summary>
/// A MediatR stream pipeline behavior that automatically validates a streaming request
/// using FluentValidation before yielding responses from its handler.
/// </summary>
/// <typeparam name="TRequest">The type of the streaming request message.</typeparam>
/// <typeparam name="TResponse">The type of each response element yielded by the handler.</typeparam>
public class StreamRequestValidationBehavior<TRequest, TResponse>(
IServiceProvider serviceProvider,
ILogger<StreamRequestValidationBehavior<TRequest, TResponse>> logger
Expand All @@ -58,6 +78,14 @@ ILogger<StreamRequestValidationBehavior<TRequest, TResponse>> logger
private readonly IServiceProvider _serviceProvider =
serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));

/// <summary>
/// Handles the incoming streaming request by validating it (if a validator is registered)
/// and then invoking the next handler in the pipeline to yield responses.
/// </summary>
/// <param name="message">The streaming request message to be handled.</param>
/// <param name="cancellationToken">A token to observe while waiting for the task to complete.</param>
/// <param name="next">The delegate representing the next step in the streaming pipeline.</param>
/// <returns>An asynchronous stream of responses yielded by the request handler.</returns>
public async IAsyncEnumerable<TResponse> Handle(
TRequest message,
CancellationToken cancellationToken,
Expand Down