PipelineBehavior in MediatR using .NET with CQRS
jayamoorthi parasuraman
FSD|Azure|Cosmos, postgresql, docker,|AKS|DevOps|Dot.Net Core/.Net5/6/7/8 ||xunit,nunit, integration, graphQL,gRpc | EFCore|API |WCF| Angular/React |Microservices,DDD/TDD| Dapper | Sonar Mob: +919715783720/+6580537622
Git Source: https://github.com/jayamoorthi/ApiDIScrutor
What are Pipeline behaviours
Pipeline behaviours are a type of middleware that get executed before and after a request. They are extremely useful for defining common cross cutting concern logic.
MediatR pipeline behaviours work in much the same way as you would expect from ASP.net core middleware. ,which is software that's assembled into an application pipeline to handle Requests and Responses. ?Each middle-ware component in the request pipeline is responsible for invoking the next component in the pipeline or short-circuiting the pipeline.?
Pipeline behaviours enable you to chain multiple behaviours together, so that each behaviour gets executed until it reaches the end of the chain and then the actual request handler is executed, then the response of which is then passed back up the chain.
Uses :
Pipeline Behaviours enable us to implement Separation of Concerns software design principle , which is an important software architecture that aims to ensure that code is separated into layers and components that each have distinct functionality with as little overlap as possible.
you can use MediatR to help implement clean code practices and elements of Aspect Oriented Programming (AOP) with MediatR.
What is Aspect Oriented Programming
Aspect-oriented programming (AOP) is a programming paradigm that isolates the supporting functions from the main program business logic.
supporting modularisation of concerns at the source code level, but it may also refer to the entire software engineering discipline.
This process of intersection, also known as weaving, occurs at build or runtime.
Weaving helps in a number of processes, such as:
What are Cross Cutting concerns
In a typical layered architecture for software solution, but there will always a need for common functionality to be used within layers. such as:
These features are usually applicable across all layers, therefore will often have a common implementation. Typically objectives you would want to achieve in a cross cutting concern is to
We want to do a few computations by taking up each of these methods, by turn at the following stages:
Fluent Validations
Fluent Validation?is a free Open Source .NET validation library that enables the implementation of separation of concerns and develop clean, easy to create, and maintain validation logic.
Install nuget packages
领英推荐
dotnet add package FluentValidation
dotnet add package FluentValidation.DependencyInjectionExtensions
dotnet add package Serilog.Settings.Configuration
dotnet add package Serilog.Exceptions
dotnet add package Serilog.Sinks.Seq
Adding Validator Rules :
Create a CreateActivityCommandValidator class for common validation logic from ValidationBehaior. ValidateAsync() call validation request data, each Validator class should inherit AbstractValidator<CreateActivityCommand> from Fluent Validation
CreateActivityCommandValidator.CS :
public class CreateActivityCommandValidator:AbstractValidator<CreateActivityCommand>
{
public CreateActivityCommandValidator()
{
RuleFor(x=> x.Name).NotEmpty();
RuleFor(x=> x.Type).NotEmpty();
RuleFor(x => x.Type).NotEmpty().WithMessage($"{nameof(CreateActivityCommand.Type)} is Required");
}
}
DI Registry
// Fluent Validation
builder.Services.AddValidatorsFromAssembly(typeof(Program).Assembly);
ValidationBehaviorPipeLine:
Create a ValidationBehavior.cs class Implement IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse>.
public class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse> where TResponse : class
{
//private readonly IValidator<IRequest> _validator;
private readonly ILogger<ValidationBehaviour<TRequest, TResponse>> _logger;
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehaviour(IEnumerable<IValidator<TRequest>> validators, ILogger<ValidationBehaviour<TRequest, TResponse>> logger)
{
_validators = validators;
// _validator = validator;
_logger = logger;
}
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
var requestJson = JsonSerializer.Serialize(request);
_logger.LogInformation("ValidationBehaviour Handling request : {Request}", requestJson);
var context = new ValidationContext<TRequest>(request);
var validationFailures = await Task.WhenAll(
_validators.Select(validator => validator.ValidateAsync(context)));
var errors = validationFailures
.Where(validationResult => !validationResult.IsValid)
.SelectMany(validationResult => validationResult.Errors)
.Select(validationFailure => new ValidationError(
validationFailure.PropertyName,
validationFailure.ErrorMessage)).Distinct()
.ToList();
if (errors.Any())
{
throw new ValidationException(errors);
}
var response = await next();
return response;
}
}
LoggingBehaviour
public class LoggingBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse>
{
private readonly ILogger<LoggingBehaviour<TRequest, TResponse>> _logger;
public LoggingBehaviour(ILogger<LoggingBehaviour<TRequest,TResponse>> logger)
{
_logger = logger;
}
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
var correlationId = Guid.NewGuid();
// Request Logging
// Serialize the request
var requestJson = JsonSerializer.Serialize(request);
// Log the serialized request
_logger.LogInformation("Handling request {CorrelationID}: {Request}", correlationId, requestJson);
// Response logging
var response = await next();
// Serialize the request
var responseJson = JsonSerializer.Serialize(response);
// Log the serialized request
_logger.LogInformation("Response for {Correlation}: {Response}", correlationId, responseJson);
// Return response
return response;
}
}
DI Registration :
builder.Services.AddMediatR(typeof(Program))
.AddScoped(typeof(IPipelineBehavior<,>), typeof(LoggingBehaviour<,>))
.AddScoped(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>));
All our configuration is complete and we can now see our validation working, if run the app and try execute our Post request and provide empty values
Payload:
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa5",
"name": "",
"type": "string",
"event": "string",
"request": "string"
}
Conclusion:
Implements PipeLineBehavior using mediatR library for Validation and Logging with FluentValidation, in additionally we have implement structure logging using seq.