MediatR Behavioral Pipeline: Validation
In my previous post Download Image API Using MediatR Vertical Architecture I talked about how to use MediatR to perform commands or queries however I have not explained how to integrate a validator so that you can validate first then handle the request you receive.
In this article I'm going to show you how to add a validator to your pipeline. A pipeline that in my case will only consists of two steps validation and handling.
I will be using the same set-up as my previous post so if you're going to follow the next steps make sure you complete the example in my previous post linked above.
Fluent Validation
First step we need to add a very useful and popular library which will be the validator that holds all of the rules that the request should follow. It's an important library that provides validation rules (custom and out of the box) with readable members such as NotEmpty(), Must(), etc.
Fluent Validation is not the only option you can also use Data Annotations however no other library is famously paired with MediatR like Fluent Validation.
So just add the library Fluent Validation to the needed projects and then create a class for your validator in our example I will validate the DownloadImageCommand as follows.
using FluentValidation;
using Playground.Commands;
namespace Playground.Validators
{
public class DownloadImageCommandValidator : AbstractValidator<DownloadImageCommand>
{
public DownloadImageCommandValidator()
{
RuleFor(command => command.Url)
.NotEmpty()
.WithMessage("Url is required.")
.Must(BeValidUrl)
.WithMessage("Invalid Url format.");
}
private bool BeValidUrl(string url)
{
// Add your custom URL validation logic here
// For example, you can use regular expressions or third-party libraries to validate the URL format
// Return true if the URL is valid, otherwise return false
return true;
}
}
}
As you can see, I just added some simple validation rules for the url field in the command with custom error messages.
Now let's add the behavior that scans for the validators of the request that is being processed in the pipeline.
Validation Pipeline Behavior
The next step for us is to add a pipeline behavior that has all the validators injected in it and can run all validators and only if they all pass the pipeline can continue.
The validation behavior class that you need to add has to implement IPipelineBehavior<TRequest, Tresponse> and TRequest must implement the IRequest<TResponse> interface.
using FluentValidation;
using MediatR;
namespace Playground.PipelineBehaviors
{
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
var context = new ValidationContext<TRequest>(request);
var validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken)));
var failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToList();
if (failures.Count != 0)
{
throw new ValidationException(failures);
}
return await next();
}
}
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next){// Call the next delegate in the pipeline. This will return once the handler has completed.var response = await next();// Code here will run after the handler has completed.// Do your post-processing here.return response;}
Pipeline Behavior Registration
Now there's important note regarding pipeline behavior registration which is they run in the order you register them so if you add another behavior, and you register it before validation behavior then it will run first then the validation then finally request handling.
Let's see how to register the behavior and the validator.
// Add pipeline behavior to handle validation
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
// Add validators
builder.Services.AddTransient<IValidator<DownloadImageCommand>, DownloadImageCommandValidator>();
Perfect! Now everything is set up and you can go ahead and try again the example from the previous post along with this validation.
Comments
Post a Comment