jbogard / MediatR

Simple, unambitious mediator implementation in .NET
Apache License 2.0
11.14k stars 1.18k forks source link

How to use ValidationExceptionHandler correctly? #850

Closed neozhu closed 1 year ago

neozhu commented 1 year ago

I need your help to solve my problem.

  1. I create a ValidationBehaviour to throw ValidationException when pre request. it's working.
services.AddMediatR(config=> {
            config.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly());
            config.NotificationPublisher = new ParallelNoWaitPublisher();
            config.AddOpenBehavior(typeof(AuthorizationBehaviour<,>));
            config.AddOpenBehavior(typeof(ValidationBehaviour<,>));
            config.AddOpenBehavior(typeof(MemoryCacheBehaviour<,>));
            config.AddOpenBehavior(typeof(CacheInvalidationBehaviour<,>));
            config.AddOpenBehavior(typeof(PerformanceBehaviour<,>));
        });

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

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

    public  Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
    {
        var context = new ValidationContext<TRequest>(request);
        var failrules = _validators
            .Select(validator => validator.Validate(context))
            .SelectMany(result => result.Errors)
            .Where(failrules => failrules != null)
            .ToList();
        if (failrules.Count != 0)
        {
            throw new ValidationException(failrules);
        }
        return next();
    }
}
  1. I create a ValidationExceptionHandler for ValidationException

    public class ValidationExceptionHandler<TRequest, TResponse, TException> : IRequestExceptionHandler<TRequest, TResponse, TException>
    where TRequest : IRequest<Result>
    where TException : ValidationException
    {
    private readonly ILogger<ValidationExceptionHandler<TRequest, TResponse, TException>> _logger;
    
    public ValidationExceptionHandler(ILogger<ValidationExceptionHandler<TRequest, TResponse, TException>> logger)
    {
        _logger = logger;
    }
    
    public async Task Handle(TRequest request, TException exception, RequestExceptionHandlerState<TResponse> state, CancellationToken cancellationToken)
    {
        var response = Activator.CreateInstance<TResponse>();
        if(response is Result result)
        {
            result.Succeeded = false;
            result.Errors = exception.Errors.Select(x => x.ErrorMessage).Distinct().ToArray();
            state.SetHandled(response);
        }
    }
    }

    After testing, this ValidationExceptionHandler does not subscribe to handle the ValidationException exception

Why? How should i do to get it to work

Thank you.

neozhu commented 1 year ago
//add RequestExceptionProcessorBehavior before ValidationBehaviour it's working
services.AddMediatR(config=> {
            config.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly());
            config.NotificationPublisher = new ParallelNoWaitPublisher();
            config.AddOpenBehavior(typeof(RequestExceptionProcessorBehavior<,>));
            config.AddOpenBehavior(typeof(ValidationBehaviour<,>));
            config.AddOpenBehavior(typeof(AuthorizationBehaviour<,>));
            config.AddOpenBehavior(typeof(MemoryCacheBehaviour<,>));
            config.AddOpenBehavior(typeof(CacheInvalidationBehaviour<,>));
            config.AddOpenBehavior(typeof(PerformanceBehaviour<,>));
            config.AddOpenBehavior(typeof(UnhandledExceptionBehaviour<,>));

        });
vits89 commented 1 year ago

Hey @neozhu! Which MediatR version do you use? 12? If so, does your ValidationBehaviour work?

neozhu commented 1 year ago

Hey @neozhu! Which MediatR version do you use? 12? If so, does your ValidationBehaviour work?

yes, in ver 12. now it's working,VaildationBehaviour throw vaildationexpection, and ValidationExceptionHandler going to do

vits89 commented 1 year ago

Very interesting... I have exactly the same ValidationBehavior as yours, and added MediatR and validation rules like this:

builder.Services.AddMediatR(cfg =>
{
    cfg.RegisterServicesFromAssemblyContaining<Startup>();
    cfg.AddOpenBehavior(typeof(RequestExceptionProcessorBehavior<,>));
    cfg.AddOpenBehavior(typeof(ValidationBehavior<,>));
});
builder.Services.AddValidatorsFromAssemblyContaining<Startup>();

However, ValidationBehavior doesn't work... Can't figure out what's wrong.

jbogard commented 1 year ago

Check your genetic constraints. The request types changed and if you didn't fix them per the migration guide your behavior will (silently) not be resolved.

vits89 commented 1 year ago

Changing constraint of my ValidationBehavior from

where TRequest : IRequest<TResponse>

to

where TRequest : IBaseRequest

solved my problem. Everything else according to the migration guide has been changed earlier. Thanks @jbogard!