jasontaylordev / NorthwindTraders

Northwind Traders is a sample application built using ASP.NET Core and Entity Framework Core.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Exchange MediatR with DI

mrtank opened this issue · comments

Hi!

My question is about is really MediatR necessary. I got it's zealously praised, but it does dependency injection. Which can be done with the already in place Microsoft.Extensions.DependencyInjection namespace. I have the following prototype to remove MediatR dependency>

First of all there is a cheat, because there is no built in assembly registration in Microsoft's DI framework. So I used https://github.com/JonPSmith/NetCore.AutoRegisterDi.

Northwind.WebUI.Startup

        public void ConfigureServices(IServiceCollection services)
        {
...
            // Add MediatR
            //services.AddMediatR(typeof(GetAllProductsQueryHandler).GetTypeInfo().Assembly);
            // registring the same interfaces that were necessary for MediatR. This could be nicer to register only IRequestHandler<,>s
            services.RegisterAssemblyPublicNonGenericClasses(
                    typeof(GetAllProductsQueryHandler).GetTypeInfo().Assembly)
                .Where(c => c.Name.EndsWith("Handler"))
                .AsPublicImplementedInterfaces();
            //services.AddTransient<IMediator, MyMediator>();
            //services.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestPerformanceBehaviour<,>));
            //services.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestValidationBehavior<,>));
...
        }

in all kind of controllers the change is >

Northwind.WebUI.Controllers.CategoriesController


    public class CategoriesController : BaseController // BaseController can be empty from now on.
    {
        [HttpGet]
        [ProducesResponseType(typeof(IEnumerable<CategoryPreviewDto>), (int)HttpStatusCode.OK)]
        public async Task<IActionResult> GetCategoryPreview([FromQuery] GetCategoryPreviewQuery query)
        {
            return Ok(await Mediator.Send(query)); > return Ok(await this.Send(query));
        }
    }

new class to exchange MediatR>

namespace Northwind.WebUI
{
    public static class MyMediator
    {
        public static async Task<TResponse> Send<TResponse>(this ControllerBase self, IRequest<TResponse> request, CancellationToken cancellationToken = new CancellationToken())
        {
            Type genType = typeof(IRequestHandler<,>);
            Type typeOfRequestHandler = genType.MakeGenericType(request.GetType(), typeof(TResponse));
            var serv = self.HttpContext.RequestServices.GetService(typeOfRequestHandler);
            MethodInfo mi = typeOfRequestHandler.GetMethod("Handle");
            var resp = (Task<TResponse>) mi.Invoke(serv, new object[] {request, cancellationToken});
            return await resp;
        }
}

Handlers don't have to be changed at all. The piped extra functionality (RequestPerformanceBehaviour and RequestValidationBehavior) can be piped through the preferred DI frameworks's capabilities. For example Autofac have Preparing and Ninject have OnActivation...

We can change the Send method like

        public static async Task<TResponse> Send<TRequest, TResponse>(this ControllerBase self, TRequest request, CancellationToken cancellationToken = new CancellationToken())
            where TRequest : IRequest<TResponse>
        {
            return await self
                .HttpContext
                .RequestServices
                .GetService<IRequestHandler<TRequest, TResponse>>()
                .Handle(request, cancellationToken);
            //Type genType = typeof(IRequestHandler<,>);
            //Type typeOfRequestHandler = genType.MakeGenericType(request.GetType(), typeof(TResponse));
            //var serv = self.HttpContext.RequestServices.GetService(typeOfRequestHandler);
            //MethodInfo mi = typeOfRequestHandler.GetMethod("Handle");
            //var resp = (Task<TResponse>) mi.Invoke(serv, new object[] {request, cancellationToken});
            //return await resp;
        }

but I had to redefine IRequestHandler, IResponse, ...

Why? What do you win?

Now that I continue with commenting out using MediatR;, and having self defined IRequestHandler, I see some code abnormalities. Why does the RequestHandler need a TResponse which are just Unit. It's sacrificing code clarity for obeying framework needs (Uncle Bob describes this like merry a framework with all it's quirks). In my version I have both IRequestHandler<TRequest, TResponse> and IRequestHandler<TRequest>. Latter's Handle method returns Task instead of Task<Unit>.

#122 issue come up with my version of mediating with DI doesn't compile with unresolvable request-response pair. In the current codebase Update's handle method behaves like it doesn't return anything, the controller behaves it returns a DTO. It shouldn't compile.

Hello and thanks for your suggestion.

There is certainly a good case to be made for not using MediatR. With Clean Architecture, we should be independent of frameworks. By using MediatR in the way I do, this is clearly not the case. I am forced to adhere to MediatR's constraints, such as using Unit when a request does not return a value. If we are to go down the path of creating our own implementation, we will have similar problems to solve. For example, publishing notifications with multiple handlers. It looks like you're off to a good start. You might like to take a look at this course; https://app.pluralsight.com/library/courses/cqrs-in-practice/. In the module Implementing Decorators upon Command and Query Handlers it includes an alternative approach that might help to build your implementation.

For my part, and specificaly in relation to Northwind Traders, I happy to have these tradeoffs. Recall that I'm trying to create the simplest approach to enterprise application development. MediatR provides the features that I require, and I don't need to jump through too many hoops to use it. Jimmy does a great job keeping it up to date, and when it's time for a new feature, he provides awesome instructions on upgrading - especially in relation to breaking changes.

Thanks again for the suggestion.