Mediatr: Keeping the contract request POCO

Created on 11 Nov 2020  路  2Comments  路  Source: jbogard/MediatR

Hello and thank you for a great library!
Love the simplicity and no-fuzz approach.

One thing that I would like to ask is why there is no support for POCO requests without the decorator interface IRequest.
I realize why there is such an interface but the relation between the request and response is enforced through the requesthandler.
One reason would be that you wouldn't need the handler (or similar register) to determine the relation between the request and response when i.e. generating API documentation. Could anyone elaborate on other benefits?

However, I adjusted the source on my dev machine to try out this setup. And with my requirements I couldn't find a scenario where it didn't suffice.

What I did was:

public interface IRequestHandlerTestCase<in TRequest, TResponse>
    {
        Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken);
    }

internal abstract class RequestHandlerWrapperTestCase<TRequest, TResponse> : RequestHandlerBase
    {
        public abstract Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, ServiceFactory serviceFactory);
    }

    internal class RequestHandlerWrapperImplTestCase<TRequest, TResponse> : RequestHandlerWrapperTestCase<TRequest, TResponse>
    {
        public override Task<object> Handle(object request, CancellationToken cancellationToken, ServiceFactory serviceFactory)
        {
            return Handle((IRequest<TResponse>) request, cancellationToken, serviceFactory)
                .ContinueWith(t =>
                {
                    if (t.IsFaulted)
                    {
                        ExceptionDispatchInfo.Capture(t.Exception.InnerException).Throw();
                    }
                    return (object) t.Result;
                }, cancellationToken);
        }

        public override Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken,
            ServiceFactory serviceFactory)
        {
            Task<TResponse> Handler() => GetHandler<IRequestHandlerTestCase<TRequest, TResponse>>(serviceFactory).Handle((TRequest) request, cancellationToken);

            return serviceFactory
                .GetInstances<IPipelineBehavior<TRequest, TResponse>>()
                .Reverse()
                .Aggregate((RequestHandlerDelegate<TResponse>) Handler, (next, pipeline) => () => pipeline.Handle((TRequest) request, cancellationToken, next))();
        }
    }

    public interface IMediator
    {
        Task<TResponse> SendTestCase<TRequest, TResponse>(TRequest request,
            CancellationToken cancellationToken = default);
}

public class Mediator : IMediator
{
        public Task<TResponse> SendTestCase<TRequest, TResponse>(TRequest request, CancellationToken cancellationToken = default)
        {
            var handler = Activator.CreateInstance(typeof(RequestHandlerWrapperImplWithoutExplicitResponseType<TRequest, TResponse>)) as RequestHandlerWrapperImplWithoutExplicitResponseType<TRequest, TResponse>;
            return handler.Handle(request, cancellationToken, _serviceFactory);
        }
}

        public class PingTestCase
        {
            public string Message { get; set; }
        }

        public class PingHandlerTestCase : IRequestHandlerTestCase<PingTestCase, Pong>
        {
            public Task<Pong> Handle(PingTestCase request, CancellationToken cancellationToken)
            {
                return Task.FromResult(new Pong { Message = request.Message + " Pong" });
            }
        }

        [Fact]
        public async Task Should_resolve_main_handler_no_explicit_return_type()
        {
            var container = new Container(cfg =>
            {
                cfg.Scan(scanner =>
                {
                    scanner.AssemblyContainingType(typeof(PublishTests));
                    scanner.IncludeNamespaceContainingType<PingTestCase>();
                    scanner.WithDefaultConventions();
                    scanner.AddAllTypesOf(typeof(IRequestHandlerTestCase<,>));
                });
                cfg.For<ServiceFactory>().Use<ServiceFactory>(ctx => t => ctx.GetInstance(t));
                cfg.For<IMediator>().Use<Mediator>();
            });

            var mediator = container.GetInstance<IMediator>();

            var response = await mediator.SendTestCase<PingTestCase, Pong>(new PingTestCase { Message = "Ping" });

            response.Message.ShouldBe("Ping Pong");
        }

Most helpful comment

I agree that it is nice to be able to just call the mediator without having to specify the generic arguments.
However in most of my use cases I'm not interacting with the mediator explicitly. For instance in my catch-all webapi endpoint I receive the input type. I then resolve the handler via a lookup registry (in Autofac the handler is registered with a Named argument). The response from the handler is handled as a dynamic before it is serialized by the JSON serializer before reaching the client.

I'm not sure if the problem I'm having with adding IRequest to my contract is that it would lock me in to MediatR. Of course this is already the case since I'm using the IRequestHandler...

I tried to append my code (adapted) "outside" of mediator via an extension method on IMediator and the other classes. No problem what so ever and all unit-tests (also adapted to not using the IRequest) works well.
So the extensibility of this project is good =O)
And I reached my goal of not having to decorate my contract classes with IRequest and still use pipelines, etc.

So all is good!

All 2 comments

Because you have to now supply 2 generic arguments, instead of zero. I wanted the interface to infer all generic arguments to remove the need to specify them, which is annoying.

I agree that it is nice to be able to just call the mediator without having to specify the generic arguments.
However in most of my use cases I'm not interacting with the mediator explicitly. For instance in my catch-all webapi endpoint I receive the input type. I then resolve the handler via a lookup registry (in Autofac the handler is registered with a Named argument). The response from the handler is handled as a dynamic before it is serialized by the JSON serializer before reaching the client.

I'm not sure if the problem I'm having with adding IRequest to my contract is that it would lock me in to MediatR. Of course this is already the case since I'm using the IRequestHandler...

I tried to append my code (adapted) "outside" of mediator via an extension method on IMediator and the other classes. No problem what so ever and all unit-tests (also adapted to not using the IRequest) works well.
So the extensibility of this project is good =O)
And I reached my goal of not having to decorate my contract classes with IRequest and still use pipelines, etc.

So all is good!

Was this page helpful?
0 / 5 - 0 ratings