Mediatr: Question regarding IPipelineBehavior

Created on 12 Oct 2017  Â·  12Comments  Â·  Source: jbogard/MediatR

Hi,
I'm trying to use the IPipelineBehavior in a very specific way and (probably - likely - very much) I'm missing something because it is not working.

All my requests are of type IRequest<OperationResult> or IRequest<OperationResult<TResult>>. OperationResult is a class that's basically a way for me to return a (possible) failed result, with errors, and if the result is not failed, a way to attach a value to it (the generic version).

Now, I want to add in a pipeline so I can short-circuit the flow if some condition is met.
I then defined my pipeline like this:
public sealed class AuthorizationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, OperationResult<TResponse>> where TRequest : class, IRequest<OperationResult<TResponse>> but on execution that throws because TResponse is not the type I want (the result type), but the type of the IResult.

Of course that I can't define an AuthorizationBehavior<TRequest, OperationResult<TResponse>>. That's not valid. So, I'm blind right now as I don't know how to define the pipeline in order for me to be able to short-circuit it, without throwing an exception, and be able to return my own OperationResult inside the handler if I need to...

Any hints or ideas? Maybe I'm using this wrong and I have to do it in another way?

Most helpful comment

Yes! I should blog about this more :P

On Thu, Nov 16, 2017 at 11:29 AM, Ricardo Peres notifications@github.com
wrote:

Not exactly the same, but similar: I want to register an implementation of
IPipelineBehavior for a concrete type, like:
svcs.AddScoped,
CommandPipelineBehavior>();

however, the CommandPipelineBehavior class is never resolved! But if I
register an open implementation:

svcs.AddScoped

it does! Is this supposed to?
Thanks!

—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/jbogard/MediatR/issues/201#issuecomment-344997633,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAGYMrSDw6ee-syKSxV0SbS5n5PijdbEks5s3HDtgaJpZM4P3BBb
.

All 12 comments

You can just not call "next()"?

On Thu, Oct 12, 2017 at 8:35 AM, Marcelo Volmaro notifications@github.com
wrote:

Hi,
I'm trying to use the IPipelineBehavior in a very specific way and
(probably - likely - very much) I'm missing something because it is not
working.

All my requests are of type IRequest or IRequest.
OperationResult is a class that's basically a way for me to return a
(possible) failed result, with errors, and if the result is not failed, a
way to attach a value to it (the generic version).

Now, I want to add in a pipeline so I can short-circuit the flow if some
condition is met.
I then defined my pipeline like this:
public sealed class AuthorizationBehavior :
IPipelineBehavior> where TRequest :
class, IRequest> but on execution that throws
because TResponse is not the type I want (the result type), but the type of
the IResult.

Of course that I can't define an AuthorizationBehavior OperationResult>. That's not valid. So, I'm blind right now as
I don't know how to define the pipeline in order for me to be able to
short-circuit it, without throwing an exception, and be able to return my
own OperationResult inside the handler if I need to...

Any hints or ideas? Maybe I'm using this wrong and I have to do it in
another way?

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/jbogard/MediatR/issues/201, or mute the thread
https://github.com/notifications/unsubscribe-auth/AAGYMvSrgOJKN-ZJQ_zvIbDlWvpvYwqVks5srhWzgaJpZM4P3BBb
.

Sure, but the problem is how do I make Mediator resolve the correct type based on the above? Inside the handler, I have an if(somethig) return OperationResult.FromError("Something went wrong") else return next().

That compiles if I use the definition in the first post, but Mediator is not resolving that correctly (TResult once resolved is of type OperationResult instead of simply TResult).

It might help if you only have IRequest, I think?

On Thu, Oct 12, 2017 at 1:32 PM, Marcelo Volmaro notifications@github.com
wrote:

Sure, but the problem is how do I make Mediator resolve the correct type
based on the above? Inside the handler, I have an if(somethig) return
OperationResult.FromError("Something went wrong") else return next().

That compiles if I use the definition in the first post, but Mediator is
not resolving that correctly (TResult once resolved is of type
OperationResult instead of simply TResult).

—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/jbogard/MediatR/issues/201#issuecomment-336226357,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAGYMnqpNu521bpft-zNXlMvAAKy_Juwks5srlsugaJpZM4P3BBb
.

But I need T to be of type OperationResult to be able to return the correct type (I need to construct it with OperationResult.FromError). So, what I would need is a way to know the type of OperationResult (that's why I put all the constrains on the original definition).
Again, maybe what I need it is not possible.

So, this code:

public sealed class AuthorizationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, OperationResult<TResponse>>
        where TRequest : class, IRequest<OperationResult<TResponse>>
{
    private readonly ICommandAuthorizer _authorizer;
    private readonly AccessContext _context;
    private readonly ISynchronizerUnitOfWork _unitOfWork;

    public AuthorizationBehavior(ISynchronizerUnitOfWork unitOfWork, AccessContext context, ICommandAuthorizer authorizer)
    {
        _unitOfWork = unitOfWork;
        _context = context;
        _authorizer = authorizer;
    }

    public async Task<OperationResult<TResponse>> Handle(TRequest request, RequestHandlerDelegate<OperationResult<TResponse>> next)
    {
        var resp = await _authorizer.Evaluate(_unitOfWork, _context, request);
        if (resp.IsFaulted)
        {
            return OperationResult.FromErrors<TResponse>(resp.Errors);
        }

        return await next();
    }
}

compiles but doesn't work, as Mediator throws GenericArguments[0], 'Commands.CreateFrameworkCommand', on 'Authorization.AuthorizationBehavior2[TRequest,TResponse]' violates the constraint of type 'TRequest'in the GetPipeline method, because TRespose is resolved asOperationResult, instead of simplystring(CreateFrameworkCommand isIRequest>`)

@CheloXL we do something very similar. What we did was define the OperationResult<T> as a derived class of OperationResult. that way you can define the pipeline behavior like this.

public sealed class AuthorizationBehavior<TRequest, TResponse> 
    : IPipelineBehavior<TRequest, TResponse>
        where TRequest : class, IRequest<OperationResult>
        where TResponse: class, OperationResult
{ ... }

will something like this work for you?

@lilasquared My OperationResult<T> already inherits from OperationResult and I already have an implementation similar to what you posted, but in order to return the correct type, I need to construct an OperationResult<T> when TResponse is of type OperationResult<T> and I have no way (well, I could use reflection, but I was hoping to not have to resort to that) to create an instance of OperationResult<T> without knowing T

Ah I see what you mean, I think reflection might be your best bet. This is what we have (translated to use your types).

var responseType = typeof(TResponse);
if (responseType.IsGenericType)
{
    var genericResultType = responseType.GetGenericArguments()[0];
    var resultType = typeof(OperationResult<>).MakeGenericType(genericResultType);
    return Activator.CreateInstance(resultType, resp.Errors) as TResponse;
}

@lilasquared Yep. Thanks. One thing that I could recommend is that since at runtime TResponse is known, you can put most of that code in the static ctor so you don't have to invoke reflection all the time, just the Activator.CreateInstance :wink:

Not exactly the same, but similar: I want to register an implementation of IPipelineBehavior for a concrete type, like:
svcs.AddScoped<IPipelineBehavior<Command, Command>, CommandPipelineBehavior>();

however, the CommandPipelineBehavior class is never resolved! But if I register an open implementation:

svcs.AddScoped<IPipelineBehavior<, >, GenericPipelineBehavior<, >>();

it does! Is this supposed to?
Thanks!

Yes! I should blog about this more :P

On Thu, Nov 16, 2017 at 11:29 AM, Ricardo Peres notifications@github.com
wrote:

Not exactly the same, but similar: I want to register an implementation of
IPipelineBehavior for a concrete type, like:
svcs.AddScoped,
CommandPipelineBehavior>();

however, the CommandPipelineBehavior class is never resolved! But if I
register an open implementation:

svcs.AddScoped

it does! Is this supposed to?
Thanks!

—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/jbogard/MediatR/issues/201#issuecomment-344997633,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAGYMrSDw6ee-syKSxV0SbS5n5PijdbEks5s3HDtgaJpZM4P3BBb
.

Yes, a blog, please!

I'm trying to do something similar: short-circuit the pipeline and return a CommonObject when command validation fails.

I tried code like that shared by lilasquare above but I get an error that a TResponse can't be implicitly cast as a Task. So, instead I tried this:

if (failures.Count != 0)
{
    CommonResult validationResult = 
        new CommonResult(Outcome.MessageValidationFailure, "Test error message.");

    next = new RequestHandlerDelegate<TResponse>(() =>
        {
            return 
                Activator.CreateInstance(typeof(TResponse), validationResult) as Task<TResponse>;
        });
 }

 return next();

which is accepted by the compiler but on runtime I get an exception:

NullReferenceException: Object reference not set to an instance of an object.
MediatR.Pipeline.RequestPreProcessorBehavior +< Handle > d__2.MoveNext()

How can I properly short-circuit handling of a message (command) and return a CommonResult object?

And also, thank you for your sharing of MediatR. It has made a huge difference in my application architectures!

Oops, figured it out finally!

For others to reference, here is how I setup my validation pipeline behavior to return result metadata:

public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
        where TRequest : IRequest<TResponse>
        where TResponse : CommonResult
    {
        private readonly IEnumerable<IValidator<TRequest>> _validators;
        private readonly ILogger _logger;

        public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators, ILogger logger)
        {
            _validators = validators ?? throw new ArgumentNullException(nameof(validators));
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }

        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();

            if (failures.Count != 0)
            {
                CommonResult validationResult = new CommonResult(
                    messageId: request.MessageId,
                    outcome: Outcome.MessageValidationFailure,
                    flashMessage: "A validation error occured in request " + typeof(TRequest),
                    errors: failures.ToCommonResultErrorCollection() );

                validationResult.LogErrors(typeof(TRequest), CorrelationId, _logger);

                return Task.FromResult(validationResult as TResponse);
            }

            return next();
        }
    }

Feedback is welcome!

Was this page helpful?
0 / 5 - 0 ratings