jbogard / MediatR

Simple, unambitious mediator implementation in .NET
Apache License 2.0
10.83k stars 1.16k forks source link

Question: How to register generic requests #1041

Open rezathecoder opened 2 weeks ago

rezathecoder commented 2 weeks ago

Hi I have updated the MediatR package to version 12.3.0 to use the new feature for generic requests and handlers. Is there any other change beside updating the package is needed to enable this feature? I am registering my requests like this:

services.AddMediatR(opts => {
    opts.RegisterServicesFromAssemblyContaining<BaseDto>();
});

But i get the error showing that the container can not find the implementation for my generic handlers. I have two types of generic requests. First the ones that return some data like string or dto like this:

public record GetByIdQuery<TEntity>(int Id) : IRequest<string>
    where TEntity : BaseEntity;

internal sealed class GetByIdQueryHandler<TEntity> : IRequestHandler<GetByIdQuery<TEntity>, string>
    where TEntity : BaseEntity
{

}

And then the ones that return nothing like this:

public record CreateCommand<TEntity>(int Id) : IRequest
    where TEntity : BaseEntity;

internal sealed class CreateCommandHandler<TEntity> : IRequestHandler<CreateCommand<TEntity>>
    where TEntity : BaseEntity
{

}

None of the above types are registered automatically and i have to register them like this:

services.AddTransient<IRequestHandler<GetByIdQuery<MyEntity>, string>, GetByIdQueryHandler<MyEntity>>();

services.AddTransient<IRequestHandler<CreateCommand<MyEntity>>, CreateCommandHandler<MyEntity>>();

Please help me so i can register all of my generic requests at once. @jbogard @zachpainter77

zachpainter77 commented 2 weeks ago

Well. My initial thought without being able to test out your class definitions are that you are declaring the handlers as internal and then trying to add mediatr from an assembly that doesn't have visibility to internals. The fix to this would be to change the internal visibility or to make internals visible to the other calling assembly. I don't know if this is the exact cause or not. I will confirm this soon. But, this is my initial thought.

zachpainter77 commented 2 weeks ago

Yes I do believe the issue with your handlers is that you are declaring the handler classes as internal. I am guessing you are calling AddMediatR from your .net core presentation layer project, such as, mvc, or blazor, or razor pages, etc... Probably from the Program.cs class. The issue is that your presentation layer, being in a separate assembly cannot see the internal handler when doing the assembly scanning and therefore never registers those handlers.

You will need to update the handler's access modifier to allow other assemblies to see it. Here is more info.

Another option is to make those internals visible to another assembly. You can do this by adding this to the library you want to make visible.

[assembly: InternalsVisibleTo("NameOfAssemblyYouWantToMakeLibraryVisibleTo")]
rezathecoder commented 2 weeks ago

Yes I do believe the issue with your handlers is that you are declaring the handler classes as internal. I am guessing you are calling AddMediatR from your .net core presentation layer project, such as, mvc, or blazor, or razor pages, etc... Probably from the Program.cs class. The issue is that your presentation layer, being in a separate assembly cannot see the internal handler when doing the assembly scanning and therefore never registers those handlers.

You will need to update the handler's access modifier to allow other assemblies to see it. Here is more info.

Another option is to make those internals visible to another assembly. You can do this by adding this to the library you want to make visible.

[assembly: InternalsVisibleTo("NameOfAssemblyYouWantToMakeLibraryVisibleTo")]

No sir this is not the issue. The assembly that contains the internal handlers is responsible for registering mediatr and has access to them. The same approach is applied for non-generic requests and handlers. That means public requests and internal handlers but those are registered successfully and it's working

zachpainter77 commented 2 weeks ago

So I can see that your sample registers handlers in the assembly that contains the BaseDto class. Are you saying that the base dto assembly has access to the internals of the assembly that contains the handlers? Are your handlers in the same assembly as the BaseDto type? Maybe it would help me understand more if you shared your project architecture and structure and note which classes are in what assembly.

On Sat, Jun 22, 2024, 1:35 PM rezathecoder @.***> wrote:

Yes I do believe the issue with your handlers is that you are declaring the handler classes as internal. I am guessing you are calling AddMediatR from your .net core presentation layer project, such as, mvc, or blazor, or razor pages, etc... Probably from the Program.cs class. The issue is that your presentation layer, being in a separate assembly cannot see the internal handler when doing the assembly scanning and therefore never registers those handlers.

You will need to update the handler's access modifier to allow other assemblies to see it. Here https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/access-modifiers is more info.

Another option is to make those internals visible to another assembly. You can do this by adding this to the library you want to make visible.

[assembly: InternalsVisibleTo("NameOfAssemblyYouWantToMakeLibraryVisibleTo")]

No sir this is not the issue. The assembly that contains the internal handlers is responsible for registering mediatr and has access to them. The same principles is done for non-generic requests and handlers. That means public requests and internal handlers but those are registered successfully and it's working

— Reply to this email directly, view it on GitHub https://github.com/jbogard/MediatR/issues/1041#issuecomment-2184181245, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACL2QUS5LQVLXMMINVLXMZTZIXNZFAVCNFSM6AAAAABJNYPNIKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCOBUGE4DCMRUGU . You are receiving this because you were mentioned.Message ID: @.***>

rezathecoder commented 2 weeks ago

So I can see that your sample registers handlers in the assembly that contains the BaseDto class. Are you saying that the base dto assembly has access to the internals of the assembly that contains the handlers? Are your handlers in the same assembly as the BaseDto type? Maybe it would help me understand more if you shared your project architecture and structure and note which classes are in what assembly. On Sat, Jun 22, 2024, 1:35 PM rezathecoder @.> wrote: Yes I do believe the issue with your handlers is that you are declaring the handler classes as internal. I am guessing you are calling AddMediatR from your .net core presentation layer project, such as, mvc, or blazor, or razor pages, etc... Probably from the Program.cs class. The issue is that your presentation layer, being in a separate assembly cannot see the internal handler when doing the assembly scanning and therefore never registers those handlers. You will need to update the handler's access modifier to allow other assemblies to see it. Here https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/access-modifiers is more info. Another option is to make those internals visible to another assembly. You can do this by adding this to the library you want to make visible. [assembly: InternalsVisibleTo("NameOfAssemblyYouWantToMakeLibraryVisibleTo")] No sir this is not the issue. The assembly that contains the internal handlers is responsible for registering mediatr and has access to them. The same principles is done for non-generic requests and handlers. That means public requests and internal handlers but those are registered successfully and it's working — Reply to this email directly, view it on GitHub <#1041 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACL2QUS5LQVLXMMINVLXMZTZIXNZFAVCNFSM6AAAAABJNYPNIKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCOBUGE4DCMRUGU . You are receiving this because you were mentioned.Message ID: @.>

Yes the BaseDto and all requests and request handlers are in the same assembly called Application layer. The follwing code which is responsible of registering MediatR is also located in Application layer:

public static void RegisterApplicationLayer(this IServiceCollection services) {
    services.AddMediatR(opts => {  
    opts.RegisterServicesFromAssemblyContaining<BaseDto>();
});
}

Then in program.cs file in Api layer i simply call:

builder.Services.RegisterApplicationLayer();

Non-generic requests and handlers are registered fine but the generics not

zachpainter77 commented 1 week ago

Hmm.. Ok.. So if what you say is accurate then I have no idea why the handlers are not being registered.

In my testcase for this specific issue I created a class in a separate project to be used as your "ApplicationLayer" example.

 public class BaseEntity
 {
     public int Id { get; set; }       
 }

 public class  Entity : BaseEntity
 {

 }

 public record GetByIdQuery<TEntity>(int Id) : IRequest<string>
 where TEntity : BaseEntity;

 internal sealed class GetByIdQueryHandler<TEntity> : IRequestHandler<GetByIdQuery<TEntity>, string>
     where TEntity : BaseEntity
 {
     public Task<string> Handle(GetByIdQuery<TEntity> request, CancellationToken cancellationToken)
     {
         return Task.FromResult(request.Id.ToString());
     }
 }

 public static class Registration
 {
     public static IServiceCollection RegisterApplicationLayer(this IServiceCollection services)
     {
         return services.AddMediatR(opts => opts.RegisterServicesFromAssemblyContaining<BaseEntity>());

     }
 }

This contains the BaseEntity definition, a class Entity that extends that base class, the generic request definition with constraints, the generic handler definition for that request, and a static registration extension method that calls registers mediatR like you suggest above.

Lastly I simply call the extension method from my Program.cs file in my presentation layer:

builder.Services.RegisterApplicationLayer();

In my presentation layer I then call the mediatR request in an action method on a controller like so:

 public async Task<IActionResult> Index()
 {            
     var vm = new HomeViewModel
     {                
         IdValue = await _mediator.Send(new GetByIdQuery<Entity>(1))
     };
     return View(vm);
 }

The result is successful and the handler is registered. The view shows the input id that is stored on the view model.

So I'm really not sure what else could be different between my test and your issue? It seems to work as designed on my end.