Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Manual registration of generic handler works, but automatic doesn't. #1087

Open
matteo-mosca opened this issue Nov 17, 2024 · 2 comments
Open

Comments

@matteo-mosca
Copy link

If I make the following registration:

builder.Services.AddScoped<IRequestHandler<CreateCommand<CreateClientDto, ClientEntity>, Result<ClientEntity>>, CreateHandler<CreateClientDto, ClientEntity>>();

Then at runtime everything is resolved and works. But if I leave it to automatic registration with RegisterGenericHandlers = true I have some weird error (namespaces redacted for brevity, everything is in the same assembly):

System.InvalidOperationException: No service for type 'MediatR.IRequestHandler2[CreateCommand2[CreateClientDto,ClientEntity],Result1[ClientEntity]]' has been registered.`

Which is extremely weird, because manual registration of the very same works.

Am I missing something? I can provide more code if anyone thinks it matters, I honestly think it doesnt, like my classes implementations and such.

@zachpainter77
Copy link
Contributor

zachpainter77 commented Jan 15, 2025

usually this error occurs when you have your handlers and requests in separate assemblies.
Also, the types that you pass as generic type parameters need to be added as well. if this is the case then you need to add all assemblies to the AddMediatR method.

Edit: The reason for this, is that to register generic handlers, you must register the concrete handler, just like you did manually. However, to truly take advantage of the generic you would also need to manually register each and every concrete handler you intend to use (for each possible type you intend to use as a parameter to T). The RegisterGenericHandlers feature intends to do this for you by scanning the assemblies for all types that can close the generic requests and handlers and then registers that specific combination for you. This removes the need to tirelessly manually register the generic commands and handlers manually / individually. However, if you do not use type constraints on the generic parameters then it can cause performance issues (At startup), since it tries to register every combination of the request/handler. Type constraints limit the possible combinations that can close that specific implementation. Also you can't really take advantage of generic methods unless you use type constraints. Type constraints will allow you to code to the abstraction. Hope that helps.. Cheers.

@zachpainter77
Copy link
Contributor

I tested like this:

requests and handlers ->

namespace TestApplication
{
    public class GenericRequest<T> : IRequest<int>
    {
        public T Dto { get; set; }
    }


    public class GenericHandler<T> : IRequestHandler<GenericRequest<T>, int>
    {
        Task<int> IRequestHandler<GenericRequest<T>, int>.Handle(GenericRequest<T> request, CancellationToken cancellationToken)
        {
            return Task.FromResult(1);
        }
    }
}

domain layer (dto) ->

namespace TestDomain
{
    public class MyDto
    {

    }
}

registration (Program.cs in mvc project)

using TestApplication;
using TestDomain;

builder.Services.AddMediatR(cfg =>
{
    cfg.RegisterGenericHandlers = true;
    cfg.RegisterServicesFromAssemblies([typeof(MyDto).Assembly, typeof(GenericHandler<>).Assembly]);
});

MVC controller ->

using MediatR;
using Microsoft.AspNetCore.Mvc;
using TestApplication;
using TestDomain;

namespace TestMediatRMVC.Controllers
{
    public class HomeController : Controller
    {
      
        private readonly IMediator _mediator;

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

        public async Task<IActionResult> Index()
        {
            var request = new GenericRequest<MyDto>
            {
                Dto = new MyDto()
            };
            var result = await _mediator.Send(request);
            return View(result);
        }
    }
}

Index.cshtml (view) ->

@model int
@{
    ViewData["Title"] = "Home Page";
}

<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <p>Learn about <a href="https://learn.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

<p>@Model</p>

Result in browser ->
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants