jbogard / MediatR

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

Is it possible to use Result objects in IPipelineBehaviourHandlers ? #906

Closed pillepalle1 closed 1 year ago

pillepalle1 commented 1 year ago

My project uses https://github.com/mcintyre321/OneOf (similar to Result objects) to avoid throwing exceptions. It works well with IRequest<OneOf<T, ProblemDetails>> and IRequestHandler<TRequest,OneOf<T,ProblemDetails>> , but now I am implementing validation via IPipelineBehaviour<TRequest, TResult> and want to return ProblemDetails if validation fails and next() if the validation succeeds. The problem here is that, even though TResult is guaranteed to be OneOf<T,ProblemDetails> for some T, the method Handle(...) has no way of knowing that and hence does not let me return objects the way I want.

I tried declaring it as public class ValidationBehaviour<TRequest,TResult> : IPipelineBehaviour<TRequest,OneOf<TResult,ProblemDetails>> but in that case the handler is not invoked, even though registered as services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>));

The same would go with some Result<T>. Is there a workaround for this issue or is there no way around throwing Exceptions ?

pillepalle1 commented 1 year ago

I suppose a more generalised version of this question is: Is it possible to swap out the return object in an implementation of IPipelineBehaviour ? Since the handler is registered as an open generic IPipelineBehaviour<,>, there seems to not really a way to make assertion about TResult other in the where clause in the class declaration, which cannot be used, because then we don't have the inner type, ie.

public class MyBehaviour<TRequest,TResult> : IPipelineBehaviour<TRequest,TResult>
    where TRequest : IRequest<TResult>
    where TResult : OneOf<???,Problem>
{
    // Implementation goes here
}

The ??? denotes information that is missing. Is there a mechanism implemented to make this kind of assertion to the implementation?

If registered with concrete types, it works, but that is then pointless as there would be one handler registered for each Query or Command

services.AddTransient(typeof(IPipelineBehaviour<DevCmd,Oneof<Object,ProblemDetails>>), typeof(ValidationBehaviour<OneOf<Object,ProblemDetails>>);