question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Returning Result<T> from a behaviour.

See original GitHub issue

MediatR version: 9.0.0 .NET target framework: net5.0 dotnet version: 5.0.201

I’m trying to implement FluentValidation without throwing an exception, and to do that I’m using a Result<T> type. I have the following Query:

public class TestQuery
{
    public record Query(string? TestString) : IRequest<Result<string>>;

    public class QueryValidator : AbstractValidator<Query>
    {
        public QueryValidator()
        {
            RuleFor(x => x.TestString)
                .NotEmpty();
        }
    }
    
    public class Handler : RequestHandler<Query, Result<string>>
    {
        protected override Result<string> Handle(Query request)
        {
            return request.TestString!;
        }
    }

and the behaviour written like so:

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

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

    public Task<Result<TResponse>> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<Result<TResponse>> next)
    {
        var context = new ValidationContext<TRequest>(request);
        var failures = _validators
            .Select(x => x.Validate(context))
            .SelectMany(x => x.Errors)
            .Where(x => x is not null)
            .ToList();

        if (failures.Any())
        {
            return Task.FromResult(Result<TResponse>.FromError(new GenericError("test")));
        }

        return next();
    }
}

and I register the behaviour with DI like so:

services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>));

However the behaviour never runs when I run the query (I have verified with the debugger). What gives?

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:13 (2 by maintainers)

github_iconTop GitHub Comments

3reactions
defunkycommented, Jun 29, 2021

Is there no way around this aside from reflection? How do people normally handle validation errors without throwing exceptions?

1reaction
kevbrycommented, Mar 30, 2021

I think the issue is right here: https://github.com/jbogard/MediatR/blob/50af3de7fa89d56067952581179fa22a1b66de0b/src/MediatR/Wrappers/RequestHandlerWrapper.cs#L63

When mediatr is trying to locate your behaviours, it’s asking whichever DI adapter you’re using for the set of IPipelineBehavior<TRequest,TResponse> implementations it has registered. In this case, in RequestHandlerWrapperImpl, TRequest will be of type Query and TResponse will be of type Result<string>. This is an issue, because when RequestHandlerWrapperImpl asks DI for an IBehavior, it’s going to try to instantiate ValidationBehaviour using the generic parameters <Query, Result<string>> and not the <Query,string> you were probably expecting it to ask for. If you turn on a first chance exception handler or look in the visual studio exception timeline you’ll see an exception that looks like this (I changed the names of TRequest and TResponse in your behavior to TReq and TResp to differentiate them from the names in RequestHandlerWrapperImpl):

Exception thrown: ‘System.ArgumentException’ in System.Private.CoreLib.dll (“GenericArguments[0], TestQuery+Query’, on ‘ValidationBehaviour2[TReq,TResp]’ violates the constraint of type ‘TReq’.”) Exception thrown: ‘System.ArgumentException’ in System.Private.CoreLib.dll (“GenericArguments[0], ‘TestQuery+Query’, on ‘ValidationBehaviour`2[TReq,TResp]’ violates the constraint of type ‘TReq’.”)

I’m guessing your behavior registration looked like this (assuming you’re using the microsoft DI container): services.AddTransient(typeof(IPipelineBehavior<,>),typeof(ValidationBehaviour<,>));

If you change it to the following, you’ll probably see it start working: services.AddTransient(typeof(IPipelineBehavior<Query,Result<string>>), typeof(ValidationBehaviour<Query,string>));

If you want to not have to manually register every possible Result<T> => ValidationBehavior<?,T> combination, I think you’re going to have to resort to reflection (what I did), or find a container that does the work of creating those closed implementation types for you. If there’s another way to do it, I’d love to hear it. I’m fairly sure I remember seeing a bug in here a couple of years back that had some gnarly reflection code to accomplish this - I’ll update if I can find it.

Edit: There are probably a dozen bugs here that are all asking variations of this question (just search for IPipelineBehavior). Most are in the simple case (TResponse isn’t generic) that’s been solved in .net 5.0 now that open generic registration is allowed, but quite a few are in your (and my) category where TResponse is generic. Here are a few of the latter case: https://github.com/jbogard/MediatR/issues/562 https://github.com/jbogard/MediatR/issues/583 https://github.com/jbogard/MediatR/issues/466 https://github.com/jbogard/MediatR/issues/305 https://github.com/jbogard/MediatR/issues/200

Read more comments on GitHub >

github_iconTop Results From Across the Web

1.4 "return result" behavior question
When the return result; statement is inside of the switch(movie) {} sequence, then there is no function to determine what result should be...
Read more >
c++ - Function with missing return value, behavior at runtime
Flowing off the end of a function is equivalent to a return with no value; this results in undefined behavior in a value-returning...
Read more >
What's your return on behaviour?
I explained “There are three kinds of behavior - productive, denying and unproductive.” “We're going to focus on the unproductive behaviours ...
Read more >
Using the values returned by behaviour formulas (ie Patch)
Solved: How can we read/use the values returned by behaviour formulas such as Patch? The reference article mentions this: " The return value...
Read more >
Retaining or returning? Some insights for a better ...
Some insights for a better understanding of return behaviour ... This paper details an empirical study of return behaviour based on a field ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found