0

I've just started to use MediatR in an asp.net core project and am struggling to wire up validation ...

Here's my controller:

public class PersonController : Controller
{
    IMediator mediator;
    public PersonController(IMediator mediator)
    {
        this.mediator = mediator;
    }

    [HttpPost]
    public async Task<ActionResult> Post([FromBody]CreatePerson model)
    {
        var success = await mediator.Send(model);
        if (success)
        {
            return Ok();
        }
        else
        {
            return BadRequest();
        }
    }
}

... and the CreatePerson command, validation (via FluentValidation) and request handler:

public class CreatePerson : IRequest<bool>
{
    public string Title { get; set; }

    public string FirstName { get; set; }

    public string Surname { get; set; }
}

public class CreatePersonValidator : AbstractValidator<CreatePerson>
{
    public CreatePersonValidator()
    {
        RuleFor(m => m.FirstName).NotEmpty().Length(1, 50);
        RuleFor(m => m.Surname).NotEmpty().Length(3, 50);
    }
}

public class CreatePersonHandler : IRequestHandler<CreatePerson, bool>
{

    public CreatePersonHandler()
    {
    }

    public bool Handle(CreatePerson message)
    {
        // do some stuff
        return true;
    }

}

I have this generic validation handler:

public class ValidatorHandler<TRequest, TResponse> : IRequestHandler<TRequest, TResponse> where TRequest : IRequest<TResponse>
{
    private readonly IRequestHandler<TRequest, TResponse> inner;
    private readonly IValidator<TRequest>[] validators;

    public ValidatorHandler(IRequestHandler<TRequest, TResponse> inner, IValidator<TRequest>[] validators)
    {
        this.inner = inner;
        this.validators = validators;
    }

    public TResponse Handle(TRequest message)
    {
        var context = new ValidationContext(message);

        var failures = validators
            .Select(v => v.Validate(context))
            .SelectMany(result => result.Errors)
            .Where(f => f != null)
            .ToList();

        if (failures.Any())
            throw new ValidationException(failures);

        return inner.Handle(message);
    }
}

... but I'm struggling to wire the validation up correctly in Startup.ConfigureServices using autofac:

public IServiceProvider ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc();

    var builder = new ContainerBuilder();

    builder.Register<SingleInstanceFactory>(ctx =>
    {
        var c = ctx.Resolve<IComponentContext>();
        return t => c.Resolve(t);
    });
    builder.Register<MultiInstanceFactory>(ctx =>
    {
        var c = ctx.Resolve<IComponentContext>();
        return t => (IEnumerable<object>)c.Resolve(typeof(IEnumerable<>).MakeGenericType(t));
    });

    builder.RegisterAssemblyTypes(typeof(IMediator).GetTypeInfo().Assembly).AsImplementedInterfaces();
    builder.RegisterAssemblyTypes(typeof(CreatePersonHandler).GetTypeInfo().Assembly).AsClosedTypesOf(typeof(IRequestHandler<,>));

    builder.RegisterGenericDecorator(typeof(ValidatorHandler<,>), typeof(IRequestHandler<,>), "Validator").InstancePerLifetimeScope();

    builder.Populate(services);

    var container = builder.Build();
    return container.Resolve<IServiceProvider>();
}

When I run the app and POST /api/person { "title": "Mr", "firstName": "Paul", "surname": "" }

I get a 200. CreatePersonHandler.Handle() was called but CreatePersonValidator() is never called.

Am i missing something in Startup.ConfigureServices()?

Carl Rippon
  • 4,553
  • 8
  • 49
  • 64

1 Answers1

0

I suggest that you read the official documentation on how to wire up decorators in Autofac.

Decorators use named services to resolve the decorated services.

For example, in your piece of code:

builder.RegisterGenericDecorator(
    typeof(ValidatorHandler<,>),
    typeof(IRequestHandler<,>),
    "Validator").InstancePerLifetimeScope();

you're instructing Autofac to use ValidationHandler<,> as a decorator to IRequestHandler<,> services that have been registered with the Validator name, which is probably not what you want.

Here's how you could get it working:

// Register the request handlers as named services
builder
    .RegisterAssemblyTypes(typeof(CreatePersonHandler).GetTypeInfo().Assembly)
    .AsClosedTypesOf(typeof(IRequestHandler<,>))
    .Named("BaseImplementation");

// Register the decorators on top of your request handlers
builder.RegisterGenericDecorator(
    typeof(ValidatorHandler<,>),
    typeof(IRequestHandler<,>),
    fromKey: "BaseImplementation").InstancePerLifetimeScope();

I find specifying the name of the fromKey parameter helps in understanding how decorators work with Autofac.

Mickaël Derriey
  • 12,796
  • 1
  • 53
  • 57