Search code examples
c#asp.net-coredependency-injectionmediatr

How do I register a request with a generic type in MediatR


I have the following class

SearchQuery<TEntity>

And it's used with MediatR like this on a controller:

await Mediator.Send(
    new SearchQuery<TEntity>(
        new PageInformation(pageNumber, pageSize), Request.Query));

And I've registered MediatR with the service collection in my app as follows:

services.AddMediatR(Assembly.GetExecutingAssembly());

The SearchQuery class is in the correct namespace, it's in the same place as every other one which has been created, but when I run the application, I receive the following error.

MediatR.IRequestHandler2[AssetManagement.Core.Application.Search.Queries.SearchQuery1[AssetManagement.Core.Domain.Entities.TEntity],AssetManagement.Core.Application.Models.PaginatedList`1[AssetManagement.Core.Domain.Entities.TEntity]]. Register your handlers with the container. See the samples in GitHub for examples

The SearchQuery<T> class in detail:

public class SearchQuery<T> : IRequest<PaginatedList<T>>
{
    public SearchQuery(
        PageInformation pageInformation,
        IQueryCollection searchQueryCollection)
    {
        QueryCollection = searchQueryCollection;
        PageInformation = pageInformation;
    }
    public IQueryCollection QueryCollection { get; }
    public PageInformation PageInformation { get; }
}

public class SearchQueryHandler<T>
    : IRequestHandler<SearchQuery<T>, PaginatedList<T>>
{
    private readonly IFormsDbContext _formsContext;

    public SearchQueryHandler(IFormsDbContext formsContext)
    {
        _formsContext = formsContext;
    }

    public Task<PaginatedList<T>> Handle(
        SearchQuery<T> request, CancellationToken cancellationToken)
    {
        //return PaginatedList<Entity> here?
        ...
    }
}

I've tried to register the type with the service collection as below

services.AddTransient(
    typeof(IRequestHandler<,>),
    typeof(SearchQueryHandler<TEntity>));

But then receive the following error:

System.ArgumentException: 'Open generic service type 'MediatR.IRequestHandler`2[TRequest,TResponse]' requires registering an open generic implementation type

I have tried various other ways of resolving this, but I feel I'm going around in circles.


Solution

  • What you are trying to achieve is not possible with MS.DI. You are trying to map the open-generic interface IRequestHandler<TRequest, TResponse> to an open-generic implementation SearchQueryHandler<T>. But with your desired mapping, the generic type arguments of IRequestHandler<TRequest, TResponse>, namely TRequest and TResponse, do not exactly map to that of SearchQueryHandler<T>.

    When it comes to generics, MS.DI is very simplistic. To be able to make a mapping, MS.DI must be able to fill in the generic type arguments of the abstraction directly into the created implementation. To make it less abstract, MS.DI simply calls .MakeGenericType() similar to the following code:

    Type registeredOpenImplementation = typeof(SearchQueryHandler<>);
    
    Type requestedAbstraction =
        typeof(IRequestHandler<SearchQuery<Customer>, PaginatedList<Customer>>);
    
    Type closedImplementationToResolve =
        registeredOpenImplementation
            .MakeGenericType(requestedAbstraction.GetGenericTypeArguments());
    

    The call to MakeGenericType, however, would completely fail, because:

    1. The incorrect number of types is supplied. requestedAbstraction.GetGenericTypeArguments() returns two types, while registeredOpenImplementation has one.
    2. The types do not match, because of the type constraints on SearchQueryHandler<T>. Where SearchQuery<Customer> is extracted from the abstraction, SearchQueryHandler<T> expects Customer.

    Long story short, MS.DI is not built for your use case. You have two options:

    1. Switch to a different DI Container. There are mature DI Containers that support these kinds of registrations. I can't provide you with a full list of all DI Containers that support your requirements, but I know for sure that Autofac and Simple Injector have decent support for generics.
    2. Step away from this kind of Batch-Registration and register all closed-generic types manually. For instance:
      services.AddTransient<
         IRequestHandler<SearchQuery<Customer>, PaginatedList<Customer>>,
         SearchQueryHandler<Customer>>();
      services.AddTransient<
         IRequestHandler<SearchQuery<Order>, PaginatedList<Order>>,
         SearchQueryHandler<Order>>();
      services.AddTransient<
         IRequestHandler<SearchQuery<Product>, PaginatedList<Product>>,
         SearchQueryHandler<Product>>();
      services.AddTransient<
         IRequestHandler<SearchQuery<Employee>, PaginatedList<Employee>>,
         SearchQueryHandler<Employee>>();
      // etc