Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't Mediatr resolve method when entites are in different projects?

I have a simple project to try out Mediatr issue. When the concrete class of my handler in the SAME project of my API, it WORKS. But, when I take that handler class in to a different project (and API references that project ofc), it does NOT resolve the registry.

I'm getting this error:

Handler was not found for request of type MediatR.IRequestHandler`2[MyBiz.GetTokenModelRequest,MyBiz.TokenModel]. Register your handlers with the container. See the samples in GitHub for examples.

I have this structure on my project and also shown where it works and where it doesn't:

Project structure represented as a tree view

For more clarification here are the codes:

MyApi2 -> Startup.cs:

namespace MyApi2
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            services.AddMediatR();
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseMvc();
        }
    }
}

MyApi2 -> ValuesController:

namespace MyApi2.Controllers
{
    [Route("api/[controller]")]
    public class ValuesController : Controller
    {
        private readonly IMediator _mediator;

        public ValuesController(IMediator mediator)
        {
            _mediator = mediator;
        }

        [HttpGet]
        public async Task<IEnumerable<string>> Get()
        {
            try
            {
                var rr = await _mediator.Send(new GetTokenModelRequest());
            }
            catch (Exception ex)
            {
                throw;
            }
            return new string[] { "value1", "value2" };
        }
    }
}

MyBiz -> GetTokenModelRequest

namespace MyBiz
{
    public class GetTokenModelRequest : LoginModel, IRequest<TokenModel>
    {
    }
    public class LoginModel
    {
        public string Username { get; set; }
        public string Password { get; set; }
    }
    public class TokenModel
    {
        #region Properties

        public Guid Id { get; set; }
        public string Username { get; set; }
        public string Token { get; set; }
        public DateTime Expiration { get; set; }

        #endregion
    }
}

MyInftra -> TokenQueryHandler

namespace MyInfra
{
    public class TokenQueryHandler : ITokenQueryHandler
    {
        public Task<TokenModel> Handle(GetTokenModelRequest request, CancellationToken cancellationToken)
        {
            return Task.FromResult(new TokenModel());
        }
    }
}

So, if I MOVE TokenQueryHandler from MyInfra to MyApi it works but I should be able to put it a references Project, right?

like image 629
E-A Avatar asked Mar 25 '19 11:03

E-A


People also ask

Why to use MediatR?

MediatR provides you with other functionality as well. It supports notifications mechanism. It may be very useful if you use domain events in your architecture. All classes of your events must implement INotification marker interface.

Is MediatR an anti pattern?

Most of the time it's used like a glorified Service Locator, which is notoriously an anti-pattern. Actually, no, it's never used as a service locator. No one uses the IMediator interface to locate services. They use it to dispatch requests and notifications to handlers.

What is MediatR .NET core?

MediatR Requests are very simple request-response style messages, where a single request is synchronously handled by a single handler (synchronous from the request point of view, not C# internal async/await). Good use cases here would be returning something from a database or updating a database.


1 Answers

Update

As of version 7.0.0 of the MediatR.Extensions.Microsoft.DependencyInjection package, the AppDomain no longer gets automatically scanned for loaded assemblies containing MediatR base types to register, when calling the AddMediatR() extension method.

In fact, the parameter-less overload of said function has been completely removed from the package, requiring users to pass in the assemblies (or types) to scan through instead.

This makes the registration of the MediatR base types (IRequestHandler, INotificationHandler, IRequestPreProcessor and IRequestPostProcessor) within each referenced assembly, explicitly at the user's control and discretion.

So if we have some MediatR base types in the imaginary assemblies Assembly1 and Assembly2 that we want to register with the MediatR container:

Instead of doing: services.AddMediatR();

You'll need to do: services.AddMediatR(typeof(Assembly1), typeof(Assembly2));

This makes my original answer (below) redundant for anyone using version 7 (and perhaps greater) of this package but I will keep it here for those using older versions.


Original Answer

Note: The following answer is only relevant for versions < 7.0.0 of the MediatR.Extensions.Microsoft.DependencyInjection package.

The call to the AddMediatR() extension method in your startup.cs file does many things to initialise MediatR:

  • It scans the App Domain for the currently loaded assemblies
  • It scans through each of those currently loaded assemblies to find every class that inherits from the MediatR base types (IRequestHandler, INotificationHandler, IRequestPreProcessor and IRequestPostProcessor)
  • It registers each of those MediatR base types to the container for later use

With the points above in mind, it is important to understand how the .NET CLR loads referenced assemblies. There is a really interesting blog post by Rick Strahl that goes into the details, but I will summarise it here with a quote:

In a nutshell, referenced assemblies are not immediately loaded - they are loaded on the fly as needed. So regardless of whether you have an assembly reference in a top level project, or a dependent assembly assemblies typically load on an as needed basis, unless explicitly loaded by user code. The same is true of dependent assemblies.

Why is this important to know?

Well, in your MyApi2 project, you reference the MyInfra project but you do not actually use it in any way. This means that the assembly will not get loaded by the CLR, and thus MediatR will fail to find it in the App Domain's currently loaded assemblies. As a result, your IRequestHandler will not be registered (nor any other MediatR base types in that project).

The solution to this problem is to ensure that the assembly containing the types you wish to have registered with the MediatR container is loaded before the call to AddMediatR().

You could do either of the following:

  • Manually load your referenced assembly
  • Reference a type/function that lies within your MyInfra project from your MyApi2 project

The latter option is the most typical, as you will usually have some functionality that sits in your referenced assembly that you will want to invoke (as opposed to just having an assembly that contains types).

Whichever option you go for, ensure that you do it prior to adding MediatR. Otherwise you will run into the same issue.

like image 182
Darren Ruane Avatar answered Nov 01 '22 17:11

Darren Ruane