I would greatly appreciate it if someone could help me to integrate FluentValidation into the MediatR pipeline using .NET Core's default DI container.
I have the following classes which represent my request, my handler, and my FluentValidation Validator:
`
public class SimpleRequestRequest : IRequest<string>
{
public int Id { get; set; }
}
public class SimpleRequestRequestValidator : AbstractValidator<SimpleRequestRequest>
{
public SimpleRequestRequestValidator(SimpleRequestRequest request)
{
RuleFor(m => m.Id).MustAsync(BeValidIdAsync).WithMessage("Id must be greater than 0, it is {PropertyValue}");
}
private Task<bool> BeValidIdAsync(int id, CancellationToken cancellationToken)
{
return Task.FromResult( id > 0);
}
}
public class SimpleRequestHandler : IRequestHandler<SimpleRequestRequest, string>
{
public Task<string> Handle(SimpleRequestRequest request, CancellationToken cancellationToken)
{
return Task.FromResult($"Request was for {request.Id}");
}
}
`
In order to inject FluentValidation, I have created the following behavior class:
`
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(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();
}
}
`
Within my ASP.NET Core (2.1) WebApi, I have the following controller:
`
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
private readonly IMediator _mediator;
public TestController(IMediator mediator)
{
_mediator = mediator;
}
// GET api/values/5
[HttpGet("{id}")]
public Task<string> Get(int id)
{
return _mediator.Send(new SimpleRequestRequest {Id = id});
}
}
`
What I would like to have happen is when the controller calls _mediator.Send, I would like the Validation to run. I have this working in a non .NET core solution using the IRequestPreProcessor interface. I am just trying to update things so that I can do the same type of thing within .NET Core.
I believe my struggle is in registering the services. This is in the Startup.cs file.
`
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddFluentValidation(fv=> fv.RegisterValidatorsFromAssemblyContaining(typeof(SimpleRequestRequestValidator)));
services.AddMediatR(typeof(SimpleRequestHandler).Assembly);
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
AssemblyScanner.FindValidatorsInAssembly(typeof(SimpleRequestHandler).Assembly)
.ForEach(result =>
{
Console.WriteLine("Here we are");
//services.AddScoped(result.InterfaceType, result.ValidatorType);
services.AddTransient(result.InterfaceType, result.ValidatorType);
}
);
}
`
Within this, I don't think I need to call AddFluentValidation on the AddMvc builder. I really am not trying to overwrite the validation on the Web-API controllers. I just want it on the MediatR portion.
The issue I am running into, however, is when I call the WebAPI controller. It gets to _mediator.Send, and it throws the following error:
Unable to resolve service for type 'MediatrSample.DLL.BLL.Requests.SimpleRequestRequest' while attempting to activate 'MediatrSample.DLL.BLL.Requests.SimpleRequestRequestValidator'.
It is like the DI container for .NET Core doesn't know how to create the SimpleRequestRequest to pass into the Validator.
Any thoughts or ideas? I have found a number of articles that use things like AutoFac for the DI Container, but I am trying to use the default DI in .NET Core.
Ideally, I don't want to have to register all validation and request classes. I would rather it discovers it automatically. I thought that is what I was doing with the AssemblyScanner, but apparently not.
Thank you!
FluentValidation supports MS Extensions DI with a separate package: https://fluentvalidation.net/aspnet#asp-net-core so you can copy the code from there except the part that it overrides the MVC validation.
It also could be the collection part - have you tried to resolve the validators directly from the container?
Jimmy, thank you for the response. I looked at that, but I couldn't get it to inject into the Mediatr pipeline, it seemed to only inject into the ASP.NET pipeline, if that makes sense.
I was able to get it to work using StructureMap using the following code:
`
var container = new Container();
//Configure our DI Container
container.Configure(config =>
{
// Fluent Validation
config.Scan(scanner =>
{
//Register everything in our BLL
scanner.AssemblyContainingType<SimpleRequestHandler>();
scanner.AddAllTypesOf<IValidator>();
scanner.ConnectImplementationsToTypesClosing(typeof(IValidator<>));
});
// MediatR
config.Scan(scanner =>
{
//Register everything in our BLL
scanner.AssemblyContainingType<SimpleRequestHandler>();
scanner.ConnectImplementationsToTypesClosing(typeof(IRequestHandler<,>));
scanner.ConnectImplementationsToTypesClosing(typeof(IRequestHandler<>));
scanner.ConnectImplementationsToTypesClosing(typeof(INotificationHandler<>));
});
//Pipeline
config.For(typeof(IPipelineBehavior<,>)).Add(typeof(RequestPreProcessorBehavior<,>));
config.For(typeof(IPipelineBehavior<,>)).Add(typeof(RequestPostProcessorBehavior<,>));
//Add our Validation Behavior, to run Fluent Validation, to our Mediatr pipeline...
config.For(typeof(IPipelineBehavior<,>)).Add(typeof(ValidationBehavior<,>));
config.For<IMediator>().LifecycleIs<TransientLifecycle>().Use<Mediator>();
config.For<ServiceFactory>().Use<ServiceFactory>(ctx => ctx.GetInstance);
config.Populate(services);
});
`
I agree, I think it may have something to do with resolving the validators in the DI container, but can't figure out how to do with with the default DI container in .NET Core. It may have to do with .NET Core not being able to scan assemblies and inject automatically. I think there was an Scanner add in that was able to do that. Maybe I need to go that route.
Thank you again for the feedback. For what it is worth, I really appreciate the work you put into this tool set. Looking back at some of my older code, I had been attempting CQRS but in a much less organized way. Using MediatR allows me to not only have an organized way of doing things, it also eases developer adoption and understanding. Thank you for all of your work on this.
Of course, thanks!
Although this post is already closed and there is a solution/workaround using StructureMap I wanted to mention that removing the parameter from the SimpleRequestRequestValidator constructor fixed it for me.
This is the working SimpleRequestRequestValidator class:
```C#
public class SimpleRequestRequestValidator : AbstractValidator
{
public SimpleRequestRequestValidator()
{
RuleFor(m => m.Id).MustAsync(BeValidIdAsync).WithMessage("Id must be greater than 0, it is {PropertyValue}");
}
private Task<bool> BeValidIdAsync(int id, CancellationToken cancellationToken)
{
return Task.FromResult( id > 0);
}
}
```
I know it is late to answer but u can use:
services.AddValidatorsFromAssemblyContaining(typeof(yourCommandValidator));
maybe it will be useful for other developers who will face the issue like me.
So my issue was in collection type for IValidatorUnable to resolve service for type 'System.Collections.Generic.IList1[FluentValidation.IValidator...
Finally, after replacing the IList type on IEnumerable, this error was disappeared. It means that the container can be set up to resolve only for IEnumerable collection
maybe it will be useful for other developers who will face the issue like me.
So my issue was in collection type for IValidator validators, because I was using IList and was getting the error something like -Unable to resolve service for type 'System.Collections.Generic.IList1[FluentValidation.IValidator...
Finally, after replacing the IList type on IEnumerable, this error was disappeared. It means that the container can be set up to resolve only for IEnumerable collection
Thanks a ton...
This issue was freaking me out...
I was almost on the verge of ditching fluent assertion and implement custom validators on my own..
Your suggestion saved my day...
public class ValidationBehavior
{
private readonly IEnumerable
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, 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();
}
}
in this code there in an error occur in the below line
var context = new ValidationContext(request);
to correct that it should be
var context = new ValidationContext
this in couse due to cs 0305 error
public class ValidationBehavior
: IPipelineBehavior where TRequest : IRequest
{
private readonly IEnumerable_validators; public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators) { _validators = validators; } public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, 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(); }}
in this code there in an error occur in the below line
var context = new ValidationContext(request);
to correct that it should be
var context = new ValidationContext(request);this in couse due to cs 0305 error
Please use this: var context = new ValidationContext
yes thats right sory for my mistake
You can also use this package to have it done for you:
https://github.com/GetoXs/MediatR.Extensions.FluentValidation.AspNetCore
Now what if we want to use cache along with FluentValidation?
I proceeded according to the following instruction.
https://codewithmukesh.com/blog/caching-with-mediatr-in-aspnet-core/
Now I want to use FluentValidation before caching.
Is what I mean logical at all?
Most helpful comment
maybe it will be useful for other developers who will face the issue like me. validators, because I was using IList and was getting the error something like -
So my issue was in collection type for IValidator
Unable to resolve service for type 'System.Collections.Generic.IList1[FluentValidation.IValidator...Finally, after replacing the IList type on IEnumerable, this error was disappeared. It means that the container can be set up to resolve only for IEnumerable collection