Mediatr: Mediator pipeline behavior for specific commands - the comeback

Created on 24 Sep 2020  路  15Comments  路  Source: jbogard/MediatR

After the epic journey for getting https://github.com/dotnet/runtime/pull/39540 merged, is it possible to solve these issues in the current .net 5 RC?

https://github.com/jbogard/MediatR/issues/542
https://github.com/jbogard/MediatR/issues/284

Most helpful comment

Just for anyone who need to create instance for that kind of requirements the below code may help for performant instance creation. Only supports parameterless constructors but can be extended to cover ctor parameters.

public static class New<T>
{
    public static readonly Func<T> Instance = Expression.Lambda<Func<T>>(Expression.New(typeof(T))).Compile();
}

// usage
New<T>.Instance();

All 15 comments

Yep, you just register the behaviors as you normally would.

// Startup.cs
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(PingBehavior<,>));
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(PongBehavior<,>));

// PingBehavior.cs
public class PingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : Ping
{
    public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
    {
        Debug.WriteLine("PingBehavior");
        return next();
    }
}

// PongBehavior.cs
public class PongBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : Pong
{
    public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
    {
        Debug.WriteLine("PongBehavior");
        return next();
    }
}

@Lobosque @lilasquared Is anyone working on this or can I take this?

@rohansp please go ahead, I am not doing any work related to this issue.

what is there to work on?

I am using .NET 5.0 RC2.

There is no exception with that but still it is not working. The Handle method of that pipeline behavior not being called if I wrap the TResponse with Result<TResponse>. Am I missing something?

public class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, Result<TResponse>>
        where TRequest : IRequest<Result<TResponse>>
{
    public async Task<Result<TResponse>> Handle(TRequest request, CancellationToken cancellationToken,
            RequestHandlerDelegate<Result<TResponse>> next)
    {
        return new Result<TResponse>();
    }
}

I don't know what you mean by "wrap TResponse with Result<TResponse>" can you give the example request / response objects you're working with? Also can you share your registration code?

Sure @lilasquared, thank you for your help.

public class FooRequest : IRequest<Result<FooDto>>
{
}

public class FooRequestHandler : IRequestHandler<FooRequest, Result<FooDto>>
{
}

// registration code:
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>));

Okay I thought that is what you meant just wanted to be sure. I think the problem is the use of Result<TResponse> in the definition of the class interface, though I am not in a place to test that theory. We configured ours with the result envelopes implementing an interface and then using that interface in the constraint on the result rather than the request. You do lose access to the type of the response object that way, but we build results using Activator and reflection for those cases.

    public class ExceptionBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
        where TResponse : class, IResult
    {
        public async Task<TResponse> Handle(TRequest request,
                                            CancellationToken cancellationToken,
                                            RequestHandlerDelegate<TResponse> next)
        {
            //...
        }
    }

Activator and reflection may help but those come with performance problems. Compiled lambda expressions more performant to create new instances but adding extra complexity. I am trying to handle validations without exception. But seems like I have to throw a validation exception anyway.

We handle validation without explicitly throwing any exception and just returning a failure result

Yes, it seems possible when using activator or using IResult instead of IResult<> as "TResponse".

Here is your answer for the same purpose. #466.

This was not working because the open generics not being handled on dotnet's IoC implementation before. So, why it is still not working even the open generics handled on .NET 5.0 RC?

That example uses a different DI, and is not using a constraint on the generic logging behavior. If you keep reading down I was mistaken and it was the constrained generic that was not working previously in dotnet core

Just for anyone who need to create instance for that kind of requirements the below code may help for performant instance creation. Only supports parameterless constructors but can be extended to cover ctor parameters.

public static class New<T>
{
    public static readonly Func<T> Instance = Expression.Lambda<Func<T>>(Expression.New(typeof(T))).Compile();
}

// usage
New<T>.Instance();

Following up on @lilasquared
You can also use a Flag interface i.e. IPersistent

public interface IPersistent { }

to flag all your requests for which you want to apply the behavior as such:

public class GetEntitySet : IRequest<HashSet<EntitySet>>, IPersistent {
...
}

And then finally specify for which flags what behavior.

public class PersistentBehavior<TRequest, TResponse>
    : IPipelineBehavior<TRequest, TResponse> where TRequest: IPersistent  {
    ..
}

If you don't mind using the flagging it should give you somewhat more freedom to chose what behavior applies where.

Was this page helpful?
0 / 5 - 0 ratings