Autofac Lifetime Scope Decorator - c#

I am implementing a command handler pattern using Autofac and am using it's decorator facility handle cross cutting concerns such as logging, authentication etc.
I also have dependencies that I only want scoped to the lifetime of the request / response pipeline.
I have an example implementation below:
public class Program
{
public static void Main()
{
var builder = new ContainerBuilder();
builder.RegisterAssemblyModules(typeof(HandlerModule).Assembly);
builder.RegisterType<LifetimeScopeTester>().AsSelf()
.InstancePerMatchingLifetimeScope("pipline");
var container = builder.Build();
using(var scope = container.BeginLifetimeScope("pipline")) {
var pingHandler = scope.Resolve<IHandle<PingRequest, PingResponse>>();
pingHandler.Handle(new PingRequest());
}
}
}
public class HandlerModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(ThisAssembly)
.As(type => type.GetInterfaces()
.Where(interfaceType => interfaceType.IsClosedTypeOf(typeof (IHandle<,>)))
.Select(interfaceType => new KeyedService("IHandle", interfaceType)));
builder.RegisterGenericDecorator(
typeof(SecondDecoratorHandler<,>),
typeof(IHandle<,>),
"IHandle"
)
.Keyed("SecondDecoratorHandler", typeof(IHandle<,>));
builder.RegisterGenericDecorator(
typeof(FirstDecoratorHandler<,>),
typeof(IHandle<,>),
"SecondDecoratorHandler"
);
}
}
public class LifetimeScopeTester {}
public interface IHandle<in TRequest, out TResponse>
where TRequest : class, IRequest<TResponse>
{
TResponse Handle(TRequest request);
}
public interface IRequest<TResponse> {
}
public class PingRequest : IRequest<PingResponse> {
}
public class PingResponse {
}
public class PingHandler : IHandle<PingRequest, PingResponse> {
public PingResponse Handle(PingRequest request) {
Console.WriteLine("PingHandler");
return new PingResponse();
}
}
public class FirstDecoratorHandler<TRequest, TResponse> : IHandle<TRequest, TResponse>
where TRequest : class, IRequest<TResponse>
{
private readonly IHandle<TRequest, TResponse> _decoratedHandler;
private readonly LifetimeScopeTester _lifetimeScopeTester;
public FirstDecoratorHandler(IHandle<TRequest, TResponse> decoratedHandler,
LifetimeScopeTester lifetimeScopeTester)
{
_decoratedHandler = decoratedHandler;
_lifetimeScopeTester = lifetimeScopeTester;
}
public TResponse Handle(TRequest request)
{
Console.WriteLine("FirstDecoratorHandler - LifetimeScopeTester[{0}]",
_lifetimeScopeTester.GetHashCode());
return _decoratedHandler.Handle(request);
}
}
public class SecondDecoratorHandler<TRequest, TResponse> : IHandle<TRequest, TResponse>
where TRequest : class, IRequest<TResponse>
{
private readonly IHandle<TRequest, TResponse> _decoratedHandler;
private readonly LifetimeScopeTester _lifetimeScopeTester;
public SecondDecoratorHandler(IHandle<TRequest, TResponse> decoratedHandler, LifetimeScopeTester lifetimeScopeTester)
{
_decoratedHandler = decoratedHandler;
_lifetimeScopeTester = lifetimeScopeTester;
}
public TResponse Handle(TRequest request)
{
Console.WriteLine("SecondDecoratorHandler - LifetimeScopeTester[{0}]", _lifetimeScopeTester.GetHashCode());
return _decoratedHandler.Handle(request);
}
}
As you can see, I wrap the pipleine in a scope named pipeline which means that everytime I resolve LifetimeScopeTester which is scope to pipeline, I get the same instance.
I as thinking that I might be able to replace
using(var scope = container.BeginLifetimeScope("pipline")) {
var pingHandler = scope.Resolve<IHandle<PingRequest, PingResponse>>();
pingHandler.Handle(new PingRequest());
}
with
var pingHandler = scope.Resolve<IHandle<PingRequest, PingResponse>>();
pingHandler.Handle(new PingRequest());
by creating another decorator that does that same thing.
My first instinct was:
public class LifetimeScopeDecoratorHandler<TRequest, TResponse> : IHandle<TRequest, TResponse>
where TRequest : class, IRequest<TResponse>
{
private readonly ILifetimeScope _scope;
private readonly IHandle<TRequest, TResponse> _decoratedHandler;
public LifetimeScopeDecoratorHandlerAttempt1(ILifetimeScope scope,
IHandle<TRequest, TResponse> decoratedHandler)
{
_scope = scope;
_decoratedHandler = decoratedHandler;
}
public TResponse Handle(TRequest request)
{
Console.WriteLine("LifetimeScopeDecoratorHandler");
TResponse response;
using (_scope.BeginLifetimeScope("pipeline"))
{
response = _decoratedHandler.Handle(request);
}
return response;
}
}
But the decoratedHandler would have already been resolved by the time it's injected so that won't work.
So I tried:
public class LifetimeScopeHandler<TRequest, TResponse> : IHandle<TRequest, TResponse>
where TRequest : class, IRequest<TResponse>
{
private readonly ILifetimeScope _scope;
private readonly Func<IHandle<TRequest, TResponse>> _decoratedHandlerFactory;
public LifetimeScopeHandler(ILifetimeScope scope,
Func<IHandle<TRequest, TResponse>> decoratedHandlerFactory)
{
_scope = scope;
_decoratedHandlerFactory = decoratedHandlerFactory;
}
public TResponse Handle(TRequest request)
{
Console.WriteLine("LifetimeScopeDecoratorHandler");
TResponse response;
using (_scope.BeginLifetimeScope("pipeline"))
{
var decoratedHandler = _decoratedHandlerFactory();
response = decoratedHandler.Handle(request);
}
return response;
}
}
However this repeated infinitely as calling _decoratedHandlerFactory() tries to wrap the inner handler with a LifetimeScopeHandler decorator again.
Is what I'm trying to achieve possible.
I have created a dotnetfiddle at https://dotnetfiddle.net/hwujNI demonstrating the issue.

When the Handle method of LifetimeScopeHandler class invoke the decoratedHandlerFactory delegate, it asks Autofac to resolve a IHandle<TRequest, TResponse> which is a LifetimeScopeHandler. That's why you have a StackOverflowException. We can simplify your case to this code sample :
public class Foo
{
public Foo(Func<Foo> fooFactory)
{
this._fooFactory = fooFactory;
}
private readonly Func<Foo> _fooFactory;
public void Do()
{
Foo f = this._fooFactory();
f.Do();
}
}
Even if there is a single instance of Foo you will have a StackOverflowException
In order to resolve this issue, you have to indicate Autofac that the decoratedHandlerFactory delegate of LifetimeScopeHandler should not be a delegate of LifetimeScopeHandler.
You can use the WithParameter to indicate the last decorator to use a specific parameter :
builder.RegisterGenericDecorator(
typeof(LifetimeScopeHandler<,>),
typeof(IHandle<,>),
"FirstDecoratorHandler"
)
.WithParameter((pi, c) => pi.Name == "decoratedHandlerFactory",
(pi, c) => c.ResolveKeyed("FirstDecoratorHandler", pi.ParameterType))
.As(typeof(IHandle<,>));
With this configuration, the output will be
LifetimeScopeHandler
FirstDecoratorHandler - LifetimeScopeTester[52243212]
SecondDecoratorHandler - LifetimeScopeTester[52243212]
PingHandler
By the way, you want LifetimeScopeHandler to be a special kind of decorator that will create the inner IHandler<,> in a special scope.
You can do this by asking the LifetimeScopeHandler to create the correct scope for you and resolve the previous Ihandler.
public class LifetimeScopeHandler<TRequest, TResponse>
: IHandle<TRequest, TResponse> where TRequest : class, IRequest<TResponse>
{
private readonly ILifetimeScope _scope;
public LifetimeScopeHandler(ILifetimeScope scope)
{
this._scope = scope;
}
public TResponse Handle(TRequest request)
{
Console.WriteLine("LifetimeScopeDecoratorHandler");
using (ILifetimeScope s = this._scope.BeginLifetimeScope("pipline"))
{
var decoratedHandler =
s.ResolveKeyed<IHandle<TRequest, TResponse>>("FirstDecoratorHandler");
TResponse response = decoratedHandler.Handle(request);
return response;
}
}
}
This implementation will require that LifetimeScopeHandler knows the first decorator on the chain, we can bypass that by sending the name on its constructor.
public class LifetimeScopeHandler<TRequest, TResponse>
: IHandle<TRequest, TResponse> where TRequest : class, IRequest<TResponse>
{
private readonly ILifetimeScope _scope;
private readonly String _previousHandlerName;
public LifetimeScopeHandler(ILifetimeScope scope, String previousHandlerName)
{
this._scope = scope;
this._previousHandlerName = previousHandlerName;
}
public TResponse Handle(TRequest request)
{
Console.WriteLine("LifetimeScopeDecoratorHandler");
using (ILifetimeScope s = this._scope.BeginLifetimeScope("pipline"))
{
var decoratedHandler =
s.ResolveKeyed<IHandle<TRequest, TResponse>>(previousHandlerName);
TResponse response = decoratedHandler.Handle(request);
return response;
}
}
}
And you will have to register it like this :
builder.RegisterGenericDecorator(
typeof(LifetimeScopeHandler<,>),
typeof(IHandle<,>),
"FirstDecoratorHandler"
)
.WithParameter("previousHandlerName", "FirstDecoratorHandler")
.As(typeof(IHandle<,>));
We can also bypass everything by not using the RegisterGenericDecorator method.
If we register the LifetimeScopeHandler like this :
builder.RegisterGeneric(typeof(LifetimeScopeHandler<,>))
.WithParameter((pi, c) => pi.Name == "decoratedHandler",
(pi, c) =>
{
ILifetimeScope scope = c.Resolve<ILifetimeScope>();
ILifetimeScope piplineScope = scope.BeginLifetimeScope("pipline");
var o = piplineScope.ResolveKeyed("FirstDecoratorHandler", pi.ParameterType);
scope.Disposer.AddInstanceForDisposal(piplineScope);
return o;
})
.As(typeof(IHandle<,>));
And LifetimeScopeHandler can now look like all decorator :
public class LifetimeScopeHandler<TRequest, TResponse>
: IHandle<TRequest, TResponse> where TRequest : class, IRequest<TResponse>
{
private readonly IHandle<TRequest, TResponse> _decoratedHandler;
public LifetimeScopeHandler(IHandle<TRequest, TResponse> decoratedHandler)
{
this._decoratedHandler = decoratedHandler;
}
public TResponse Handle(TRequest request)
{
Console.WriteLine("LifetimeScopeDecoratorHandler");
TResponse response = this._decoratedHandler.Handle(request);
return response;
}
}
By the way, this solution may have an issue if you use more than one IHandler<,> in a scope and you need to have a single pipline scope. To resolve this, you can see this dotnetfiddle : https://dotnetfiddle.net/rQgy2X but it seems to me over complicated and you may not need it.

Related

MediatR PipelineBehavior error message Unable to resolve service for type 'FluentValidation.IValidator`1

I have this ValidationBehavior
public sealed class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IValidator<TRequest> _validator;
public ValidationBehavior(IValidator<TRequest> validator)
{
_validator = validator;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
_validator.ValidateAndThrow(request);
return await next();
}
}
I have this handler
public class RemoveCurrencyHandler : IRequestHandler<RemoveCurrencyCommand, Unit>
{
private readonly ApplicationContext _context;
public RemoveCurrencyHandler(ApplicationContext context)
{
_context = context;
}
public async Task<Unit> Handle(RemoveCurrencyCommand request, CancellationToken cancellationToken)
{
var currency = await _context.Currency.FindAsync(request.Id);
if (currency is null)
throw new KeyNotFoundException();
_context.Remove(currency);
await _context.SaveChangesAsync();
return Unit.Value;
}
}
I'm getting error message Unable to resolve service for type 'FluentValidation.IValidator' everytime I call this handler, now obviously I know the reason is because I'm missing the validator, so it goes away if I add this
public class RemoveCurrencyValidator : AbstractValidator<RemoveCurrencyCommand>
{
}
but not all my handler need a Validator, so I don't want to add empty Validator class to handler that doesn't need it. Is there any alternative?
The MediatR validator pieplines enable you to execute validation logic before and after your Command or Query Handlers execute.
You can update the constructor of ValidationBehavior class to expect an IEnumerable<IValidator>. So that we can always resolve the class (the list will be empty if you don't implement the Validator)
// ValidationBehavior
public sealed class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IEnumrable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
if(_validators.Any())
{
var validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAndThrow(request)));
var failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToList();
if (failures.Count != 0)
throw new ValidationException(failures);
}
return await next();
}
}

MediatR - Can not create new Instance of TResponse

I am trying to create a new PipelineBehavior that returns a new Instance of TResponse.
With this code if I put a break point to the "handle" I can see that the pipeline works.
public class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
where TResponse : Response
{
private readonly ILogger<ValidationBehaviour<TRequest, TResponse>> _logger;
private readonly IValidationHandler<TRequest> _validationHandler;
public ValidationBehaviour(ILogger<ValidationBehaviour<TRequest, TResponse>> logger)
{
this._logger = logger;
}
public ValidationBehaviour(ILogger<ValidationBehaviour<TRequest, TResponse>> logger, IValidationHandler<TRequest> validationHandler)
{
this._logger = logger;
this._validationHandler = validationHandler;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var requestName = request.GetType();
_logger.LogInformation("{Request} does not have a validation handler configured.", requestName);
return await next();
}
}
but I can't have this syntax
return new TResponse { StatusCode = (int)HttpStatusCode.BadRequest, SFADevErrorMessage = result.Error };
I have seen examples where they use the "new()" keyword in the constraint and that allows them to create a new Instance of TResponse. Like that.
public class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
where TResponse : Response, new()
If I do the same I am able to do that new TResponse { StatusCode = (int)HttpStatusCode.BadRequest, SFADevErrorMessage = result.Error };
but then my pipeline is not working - is not executing and the executable code never hits my break point.
Any idea why this is happening? Am I missing something?
The actual issue was my where T : new(), had nothing to do with mediatr.
I found the answer here
The TResponse was type of Response (a class I created, for all the responses of my API) and that needed a default public constructor

Is there a way to skip MediatR Pipeline?

I would like to cache some responses from CommandsHandlers.
I Already did this using IPipelineBehaviour, but only 5% of my requests really must have cache, and the other 95% must skip this Pipeline. Is there a way to do that?
Below follows my code.
Thanks!
public class PipelineBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>, IProvideCacheKey
{
private readonly IMemoryCache _cache;
public PipelineBehavior(IMemoryCache cache)
{
_cache = cache;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken,
RequestHandlerDelegate<TResponse> next)
{
// Check in cache if we already have what we're looking for
var cacheKey = request.CacheKey;
if (_cache.TryGetValue<TResponse>(cacheKey, out var cachedResponse))
{
return cachedResponse;
}
// If we don't, execute the rest of the pipeline, and add the result to the cache
var response = await next();
_cache.Set(cacheKey, response);
return response;
}
}
public class GetUserByEmailCommand : Command, IRequest<bool>, IProvideCacheKey
{
public string Email { get; set; }
public string CacheKey => $"{GetType().Name}:{Email}";
public override bool IsValid()
{
ValidationResult = new GetUserByEmailCommandValidation<GetUserByEmailCommand>().Validate(this);
return ValidationResult.IsValid;
}
}
public interface IProvideCacheKey
{
string CacheKey { get; }
}
You can wrap your caching behavior in a check that is bypassed if the request is not cacheable to let the pipeline continue. In your case, you can probably just check if the request implements your interface at the start of the Handle method:
if (request is IProvideCacheKey)
{
// perform cache behavior, return if cached and terminate the pipeline
}
// else continue the pipeline
There's a couple good examples of this in more detail at:
https://lurumad.github.io/cross-cutting-concerns-in-asp-net-core-with-meaditr
https://anderly.com/2019/12/12/cross-cutting-concerns-with-mediatr-pipeline-behaviors/
Instead of skipping behaviors, it may be better to construct multiple pipelines with different behaviors.
Register pipeline behaviors:
// pipeline 1's behaviors
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(FooBehavior<,>));
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(BarBehavior<,>));
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(BazBehavior<,>));
// pipeline 2's behaviors
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(CatBehavior<,>));
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(DogBehavior<,>));
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(PigBehavior<,>));
Define pipelines:
// pipeline 1
public interface IPipeline1 { }
public class FooBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IPipeline1 { }
public class BarBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IPipeline1 { }
public class BazBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IPipeline1 { }
// pipeline 2
public interface IPipeline2 { }
public class CatBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IPipeline2 { }
public class DogBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IPipeline2 { }
public class PigBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IPipeline2 { }
Define requests:
// requests to be processed by pipeline 1
public class ARequest : IRequest, IPipeline1 { }
public class BRequest : IRequest, IPipeline1 { }
public class CRequest : IRequest, IPipeline1 { }
// requests to be processed by pipeline 2
public class XRequest : IRequest, IPipeline2 { }
public class YRequest : IRequest, IPipeline2 { }
public class ZRequest : IRequest, IPipeline2 { }
I faced the same problem.
I fixed it by checking in the Pipeline Behaviour Handler if the request implements the interface.
If the request implements the interface ICachable, that means that the request will go through the cachable logic.
With this change, there is no need to use the where TRequest : IQueryable .
If you leave this constrain it will throw an error when trying to
process a Query or Command that doesn't implement the IQueryable
interface.
If you are wondering how to access the interface properties the answer is by creating a variable that implements the interface.
theObject is theInterface newObjectWithInterfaceProperties
.
{
public class CacheBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
//where TRequest : ICacheableQuery
{
private readonly IMemoryCache _memoryCache;
private readonly ILogger<CacheBehaviour<TRequest, TResponse>> _logger;
public CacheBehaviour(IMemoryCache memoryCache, ILogger<CacheBehaviour<TRequest, TResponse>> logger)
{
_memoryCache = memoryCache;
_logger = logger;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken,
RequestHandlerDelegate<TResponse> next)
{
//Check if the request implements ICachableQuery
//If it does then it creates a cachableRequest variable that will contain the properties of the ICachableQuery interface.
if (request is ICachableQuery cachableRequest)
{
var requestName = request.GetType().Name;
_logger.LogInformation($"Request : {requestName} is configured to cache");
TResponse response;
if(_memoryCache.TryGetValue(cachableRequest.CacheKey, out response))
{
_logger.LogInformation($"Request: {requestName} returning response from cache");
return response;
}
response = await next();
_logger.LogInformation($"Request: {requestName} returning response from DB");
_memoryCache.Set(cachableRequest.CacheKey, response);
return response;
}
return await next();
}
}
}

How to register mediator's IPipelineBehavior with Autofac

I'm trying to understand how to set up pipeline behaviours with mediator.
My command:
public class MyUpdateCommand : IRequest<CommandResult>
{
// fields
}
My handler:
public class MyUpdateCommandHandler : RequestHandler<MyUpdateCommand, CommandResult>
{
private readonly IMyRepository _repository;
public GasDetailsUpdateCommandHandler(IMyRepository repository)
{
_repository = repository;
}
protected override CommandResult HandleCore(MyUpdateCommand command)
{
{
_repository.Update(command);
return CommandResult.Success;
}
}
}
Now, let's say I want to handle all exceptions raised in RequestHandlers that return a CommandResult by logging them and returning the message in my CommandResult object. I've created this class:
public class ExceptionLoggingBehavior<TRequest, TResponse>
: IPipelineBehavior<TRequest, TResponse> where TResponse : CommandResult
{
private readonly ILog _logger;
public ExceptionLoggingBehavior(ILog logger)
{
_logger = logger;
}
public async Task<TResponse> Handle(TRequest request,
CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
try
{
return await next();
}
catch (Exception ex)
{
_logger.Error(ex.Message);
return (TResponse) CommandResult.Fail(ex.Message);
}
}
}
What do I need to put into my inversion of control container (AutoFac)?

Add validation to a MediatR behavior pipeline?

I'm using ASP.NET Core, the built-in container, and MediatR 3 which supports "behavior" pipelines:
public class MyRequest : IRequest<string>
{
// ...
}
public class MyRequestHandler : IRequestHandler<MyRequest, string>
{
public string Handle(MyRequest message)
{
return "Hello!";
}
}
public class MyPipeline<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next)
{
var response = await next();
return response;
}
}
// in `Startup.ConfigureServices()`:
services.AddTransient(typeof(IPipelineBehavior<MyRequest,str‌​ing>), typeof(MyPipeline<MyRequest,string>))
I need a FluentValidation validator in the pipeline. In MediatR 2, a validation pipeline was created thus:
public class ValidationPipeline<TRequest, TResponse>
: IRequestHandler<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
public ValidationPipeline(IRequestHandler<TRequest, TResponse> inner, IEnumerable<IValidator<TRequest>> validators)
{
_inner = inner;
_validators = validators;
}
public TResponse Handle(TRequest message)
{
var failures = _validators
.Select(v => v.Validate(message))
.SelectMany(result => result.Errors)
.Where(f => f != null)
.ToList();
if (failures.Any())
throw new ValidationException(failures);
return _inner.Handle(request);
}
}
How do I do that now for the new version? How do I set which validator to use?
The process is exactly the same, you just have to change the interface to use the new IPipelineBehavior<TRequest, TResponse> interface.
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 Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next)
{
var context = new ValidationContext(request);
var failures = _validators
.Select(v => v.Validate(context))
.SelectMany(result => result.Errors)
.Where(f => f != null)
.ToList();
if (failures.Count != 0)
{
throw new ValidationException(failures);
}
return next();
}
}
For the validators, you should register all the validators as IValidator<TRequest> in the built-in container so they'll be injected in the behavior. If you don't want to register them one by one, I suggest that you have a look at the great Scrutor library that brings assembly scanning capabilities. This way it'll find your validators itself.
Also, with the new system, you don't use the decorator pattern anymore, you just register your generic behavior in the container and MediatR will pick it up automatically. It could look something like:
var services = new ServiceCollection();
services.AddMediatR(typeof(Program));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
var provider = services.BuildServiceProvider();
I've packed .net core integration into nuget, feel free to use it:
https://www.nuget.org/packages/MediatR.Extensions.FluentValidation.AspNetCore
Just insert in configuration section:
services.AddFluentValidation(new[] {typeof(GenerateInvoiceHandler).GetTypeInfo().Assembly});
GitHub
On the new version (MediatR (>= 9.0.0)) you can do something like this:
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 Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var context = new ValidationContext<TRequest>(request);
var failures = _validators
.Select(v => v.Validate(context))
.SelectMany(result => result.Errors)
.Where(f => f != null)
.ToList();
if (failures.Count != 0)
{
throw new ValidationException(failures);
}
return next();
}
}
Remember to add var context = new ValidationContext<TRequest>(request); in previous version like FluentApi 8.0 or below it used something like this var context = new ValidationContext(request);
for Register in Asp.Net Core in under IServiceCollection wright below code:
services.AddMediatR(Assembly.GetExecutingAssembly());
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
Hope that's helpful!

Categories

Resources