Mediatr: Issue with base response type

Created on 6 Jul 2018  路  11Comments  路  Source: jbogard/MediatR

I try to implement validation behavior in asp.net core 2.1 application.
My controller:
```C#
[HttpPost]
public async Task> Post([FromBody] CreateApplicationCommand command)
{
var response = await _mediator.Send(command);
return response;
}

ValidationBehavior:
```C#
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TResponse : CommandResponse
{
    private readonly IEnumerable<IValidator> _validators;

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

    public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
    {
        var failures = _validators
            .Select(v => v.Validate(request))
            .SelectMany(result => result.Errors)
            .Where(f => f != null)
            .ToList();

        return failures.Any()
            ? Errors(failures)
            : next();
    }

    private static Task<TResponse> Errors(IEnumerable<ValidationFailure> failures)
    {
        var response = new CommandResponse();

        foreach (var failure in failures)
        {
            response.AddError(failure.ErrorMessage);
        }

        return Task.FromResult(response as TResponse);
    }
}

Response:
```C#
public class CommandResponse
{
private readonly IList _errorMessages = new List();

public void AddError(string errorMessage)
{
    _errorMessages.Add(errorMessage);
}

public bool IsSucceed => !_errorMessages.Any();

public IReadOnlyCollection<string> Errors => new ReadOnlyCollection<string>(_errorMessages);

}

public class CommandResponse : CommandResponse where TModel : class
{
public CommandResponse(TModel model)
{
Result = model;
}

public TModel Result { get; }

}

Container registration (Asp.Net Core DI with Scrutor):
```C#
public static void AddMediator(this IServiceCollection services)
{
    services.AddScoped<ServiceFactory>(p => p.GetService);
    services.AddScoped<IMediator, Mediator>();

    services.Scan(scan => scan
        .FromAssemblies(Assembly.GetExecutingAssembly())
        .AddClasses(classes => classes.AssignableTo(typeof(IRequestHandler<,>)))
            .AsImplementedInterfaces()
            .WithScopedLifetime()
        .AddClasses(classes => classes.AssignableTo(typeof(IValidator<>)))
            .AsImplementedInterfaces()
            .WithScopedLifetime());

    services.AddScoped(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
}

If CreateApplicationCommand is valid everything works fine. When validation failure occures I expect to receive an instance of CommandResponse class with error messages. However response in controller in this case is null. What I am doing wrong?

Most helpful comment

@MaciejWierzchowski Fixed using an Activator

        private static Task<TResponse> Errors(IEnumerable<ValidationFailure> failures)
        {
            var response = Activator.CreateInstance<TResponse>();
            foreach (var failure in failures)
            {
                response.AddError(failure.ErrorMessage);
            }

            return Task.FromResult(response);
        }

Caveat : Your TResponse should have a default constructor

All 11 comments

You've verified that your validation behavior is actually executing?

Hi,

My best bet would be that you get null because of this part in ValidationBehavior:

return Task.FromResult(response as TResponse);

And the reason is that the actual type of TResponseis CommandResponse<Application>.
So you basically end up with this:

return new CommandResponse() as CommandResponse<Application>

@MaciejWierzchowski save my day! Great thanks!

@AlexanderSysoev If it's not a problem, please let me know how you end up fixing this! I'm trying to do something similiar, i.e. return a subtype of TResponse from IPipelineBehavior<TRequest, TResponse>, but then I actually get an exception from Mediator.

@MaciejWierzchowski Fixed using an Activator

        private static Task<TResponse> Errors(IEnumerable<ValidationFailure> failures)
        {
            var response = Activator.CreateInstance<TResponse>();
            foreach (var failure in failures)
            {
                response.AddError(failure.ErrorMessage);
            }

            return Task.FromResult(response);
        }

Caveat : Your TResponse should have a default constructor

24/02/2020 - Any updates on how to short-circuit the pipeline and properly handling validation results instead of throwing exceptions?

I end up implementing like this, I don't know if there's something "native" on MediatR by this time.

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

        public RequestValidationBehavior(IHttpContextAccessor httpContextAccessor, IEnumerable<IValidator<TRequest>> validators)
        {
            this.httpContextAccessor = httpContextAccessor;
            _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.Any())
            {
                this.httpContextAccessor.HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
                this.httpContextAccessor.HttpContext.Response.ContentType = "application/json";
                this.httpContextAccessor.HttpContext.Response.WriteAsync(JsonConvert.SerializeObject(new { ErrorMessages = failures.Select(x => x.ErrorMessage) }));
            }

            return next();
        }
    }

I don't think mediator has validation as a particularly implemented behavior, as you've shown you can short circuit the rest of the pipeline simply by not calling "next()" when applicable. In our use cases we return a ValidationFailedResult from the pipeline, and don't call "next()". Then we handle the http response from the controller. What other support are you looking for?

@lilasquared actually I was wondering if by this time MediatR could have something out-of-the-box, but the approaches to short-circuit are fine. In your case, how do you distinguish the ValidationFailedResult when the TResponse type is expected to be returned? Do you have this snippet to show?

I don't think you can, unless your return model derive from a generic class that has validation properties. Then you check if the return is valid or not (or if it has errors).
Very similar to what you have there, but instead of outputting it directly to the response, adding it to the return (that like I said would be something of Validated<TResponse>

Yep, basically what @joaomatossilva said. A base class of that can be Success or Failure, and the Failure result has properties for validation failure messages or any other metadata needed. All handled by a pipeline behavior that injects IValidators similar to what you have above. All request responses are wrapped in the particular Result response type and we have custom interfaces to help simplify this as well when creating commands, queries and handlers.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ArturKarbone picture ArturKarbone  路  3Comments

TDK1964 picture TDK1964  路  5Comments

developermj picture developermj  路  6Comments

mdmoura picture mdmoura  路  4Comments

kevin93w picture kevin93w  路  4Comments