PipelineBehavior in MediatR using .NET with CQRS


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 :

  • Implement common logic like Validation
  • Logging and Monitoring
  • primarily because we can write code that executes during the request enabling you to validate or perform logging duties etc.

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:

  • Replacing method bodies with new implementations
  • Inserting code before and after method calls
  • Implementing variable reads and writes
  • Associating new states and behaviours with existing classes


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:

  • Validation
  • Logging
  • Exception handling
  • Security
  • Performance
  • Auditing
  • Caching
  • Retry

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:

  • Do something before the method begins execution
  • Do something after the method completes execution
  • Track progress and execution during execution


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.


要查看或添加评论,请登录

jayamoorthi parasuraman的更多文章

  • Data Visualization (Matplot + Pandas) using Python (Jupyter NoteBook)

    Data Visualization (Matplot + Pandas) using Python (Jupyter NoteBook)

    Jupyter : Jupyter is an open-source, web-based interactive computing platform that allows you to create and share…

  • FastApi - Python

    FastApi - Python

    FastAPI is a modern, fast (high-performance), web framework for building APIs with Python. It is based on standard…

    2 条评论
  • Radis- Distributed Caching System with .NET 6 API With PostgreSqL DB

    Radis- Distributed Caching System with .NET 6 API With PostgreSqL DB

    Distributed hybrid caching as side car pattern accessing data from Reids. Distributed caching in .

  • .NET 6 with PostgreSQL and Redis Cache

    .NET 6 with PostgreSQL and Redis Cache

    To exploring how to utilise PostgreSQL with .NET 6.

  • Bogus - Generate Realistic Fake Data in C#

    Bogus - Generate Realistic Fake Data in C#

    GitSource: https://github.com/jayamoorthi/ApiDIScrutor Bogus is a fake data generator for .

  • What is Clean Code ?

    What is Clean Code ?

    The most popular definition of clean code is code that is easy to understand and easy to change. Clean code is code…

  • NET API using Scrutor DI with CQRS.

    NET API using Scrutor DI with CQRS.

    Dependency injection (DI) is built-in features of ASP.NET Core.

  • Decorator Pattern

    Decorator Pattern

    Decorator pattern is a structural design pattern that allows you to add new functionalities to an Existing object…

    1 条评论
  • Getting Started with gRPC using .Net

    Getting Started with gRPC using .Net

    RPC is a language agnostic, high-performance Remote Procedure Call (RPC) framework. Benifites: Modern…

    1 条评论
  • Value objects in DDD?

    Value objects in DDD?

    Value Objects is one of the component of DDD. Identity is fundamental for entities.

社区洞察

其他会员也浏览了