Search code examples
c#asp.net-coredomain-driven-designautofacfluentvalidation

FluentValidation Command Validator not registered by AutoFac


I've been struggling with an issue for a while now. I am building a project based on the eShopOnContainers GitHub project See Here. My project is running on asp.net core 2.2 and I am using

MediatR 6.0,

  • MediatR 6.0
  • MediatR.Extensions.Microsoft.DependencyInjection 6.0.1
  • FluentValidation.AspNetCore 8.1.2
  • Autofac.Extensions.DependencyInjection 4.3.1

I am using MediatR commands that are handled by a command handler, and through many articles, along with the eShopOnContainers sample online, I've implemented a ValidatorBehavior class that implements IPipelineBehavior.

public class ValidatorBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    private readonly IEnumerable<IValidator<TRequest>> _validators;

    public ValidatorBehavior(IEnumerable<IValidator<TRequest>> validators)
    {
        _validators = validators;
    }

    public async 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(error => error != null)
            .ToList();

        if (failures.Any())
        {
            throw new PlanningDomainException(
                $"Command Validation Errors for type {typeof(TRequest).Name}", new ValidationException("Validation exception", failures));
        }

        var response = await next();
        return response;
    }
}

I've also included a MediatorModule, just as implemented in the sample project.

public class MediatorModule : Autofac.Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterAssemblyTypes(typeof(IMediator).GetType().Assembly)
            .AsImplementedInterfaces();

        // Get the assembly name
        var assembly = typeof(Startup).GetType().Assembly;

        // Register all the Command classes (they implement IRequestHandler) in assembly holding the Commands
        builder.RegisterAssemblyTypes(assembly)
            .AsClosedTypesOf(typeof(IRequestHandler<,>));

        // Register the DomainEventHandler classes (they implement INotificationHandler<>) 
        // in assembly holding the Domain Events
        builder.RegisterAssemblyTypes(assembly)
            .AsClosedTypesOf(typeof(INotificationHandler<>));

        // Register the Command's Validators (Validators based on FluentValidation library)
        builder.RegisterAssemblyTypes(assembly)
            .Where(t => t.IsClosedTypeOf(typeof(IValidator<>)))
            .AsImplementedInterfaces();

        builder.Register<ServiceFactory>(context =>
        {
            var componentContext = context.Resolve<IComponentContext>();
            return t => { object o; return componentContext.TryResolve(t, out o) ? o : null; };
        });

        builder.RegisterGeneric(typeof(LoggingBehavior<,>)).As(typeof(IPipelineBehavior<,>));
        builder.RegisterGeneric(typeof(ValidatorBehavior<,>)).As(typeof(IPipelineBehavior<,>));
        builder.RegisterGeneric(typeof(TransactionBehaviour<,>)).As(typeof(IPipelineBehavior<,>));

    }
}

My test controller is:

[Route("api/[controller]")]
[ApiController]
public class ApplicationsController : ControllerBase
{
    private readonly IMediator _mediator;

    public ApplicationsController(IMediator mediator)
    {
        _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
    }

    [HttpPost]
    [ProducesResponseType((int)HttpStatusCode.OK)]
    [ProducesResponseType((int)HttpStatusCode.BadRequest)]
    public async Task<IActionResult> Put([FromBody]ApplicationCreateCommand command, [FromHeader(Name = "x-requestid")] string requestId)
    {
        var c = await _mediator.Send(command);
        return c ? Ok() : (IActionResult)BadRequest();
    }
}

I have the following issues:

  1. Whenever I attempt to call this API I get the following error:

    Cannot resolve parameter 'MediatR.IMediator mediator' of constructor 'Void .ctor(MediatR.IMediator)'.

I am solving this by adding mediator as a service using .AddMediatR() even though in the sample project it is never added like that.

  1. Once I add the mediatr, the API runs correctly and the command is being send and the command handled is handling it correctly. Meanwhile, the ValidatorBehavior is being called correctly, but the CommandValidator is not present. The _validators list is actually empty so no validation is being made.

I've also set up break points in the command validator but none are being hit.

This is my command validator:

public class ApplicationCreateCommandValidator : AbstractValidator<ApplicationCreateCommand>
{
    public ApplicationCreateCommandValidator()
    {
        RuleFor(cmd => cmd.CategoryType).NotEmpty().Must(BeValidCategoryType).WithMessage("The category type is not valid.");
        RuleFor(cmd => cmd.CompetitionId).NotEmpty().WithMessage("The competition id must be specified.");
        RuleFor(cmd => cmd.ParticipantId).NotEmpty().WithMessage("The participant id must be specified.");
    }

    private bool BeValidCategoryType(int categoryType)
    {
        return categoryType != 0;
    }
}

Everything should work fine! I do not understand why it wouldn't. Maybe I am not loading the command validators correctly in autofac, however, every sample code I found online point to the same registration method:

builder.RegisterAssemblyTypes(assembly)
            .Where(t => t.IsClosedTypeOf(typeof(IValidator<>)))
            .AsImplementedInterfaces();

I have the entire source code for this project on my git hub account if you would like to take a closer look. This is the API.

Can anybody help me understand what I'm doing wrong? It's been driving me nuts these past few days.


Solution

  • I have similar configuration as yours. Only difference I can find is below lines in my start.cs file

    public IServiceProvider ConfigureServices(IServiceCollection services)
            {
                services.AddMvc()
                    .AddFluentValidation(fv =>
                    {
                        fv.RegisterValidatorsFromAssemblyContaining<MediatorModule>();
                        fv.RunDefaultMvcValidationAfterFluentValidationExecutes = false;
                    }
                );
    }