Search code examples
c#genericsinversion-of-controlautofacmediatr

Autofac: Register all MediatR handlers as inner classes of generic types


Context

While trying to simplify our MediatR code, we have created generic Request/Response/Handler combinations for GetAll, GetSingle, etc.. functionalities. Below you can find the GetAll implementation

public class GetAll<T> where T : class
{
    public class Request : IRequest<Response>
    {
    }

    public class Response
    {
        public IQueryable<T> All { get; set; }
    }

    public class Handler : IRequestHandler<Request, Response>
    {
        private readonly IRepository<T> repository;

        public Handler(IRepository<T> repository)
        {
            this.repository = repository;
        }

        public Response Handle(Request message)
        {
            return new Response
            {
                All = repository.GetAll()
            };
        }
    }
}

Problem

We can't seem to register all our RequestHandlers at once using Autofac.

We are able to register a single specific type of Handler in our Autofac Module using:

builder.RegisterGeneric(typeof(GetAll<>.Handler)).AsImplementedInterfaces();

But we would like to do this for all implementations of IRequestHandler<,> (and not just the GetAll one). We've tried this using the AsClosedTypesOf function as suggested in the documentation:

builder.RegisterAssemblyTypes(typeof(GetAll<>.Request).Assembly)
              .AsClosedTypesOf(typeof(IRequestHandler<,>));

or

builder.RegisterAssemblyTypes(typeof(GetAll<>.Request).Assembly)
              .AsClosedTypesOf(typeof(IRequestHandler<,>))
              .AsImplementedInterfaces();

But this yields following exception:

Autofac.Core.Registration.ComponentNotRegisteredException: 'The requested service 'MediatR.IRequestHandler<GetAll<T>.Request,GetAll<T>.Response>' has not been registered. To avoid this exception, either register a component to provide the service, check for service registration using IsRegistered(), or use the ResolveOptional() method to resolve an optional dependency.'

What are we doing wrong (or forgetting) here?


Solution

  • You won't be able to register them at once because, as you mentioned, you have to register each of them as generics. However, there's a way for you to avoid manually keeping a list of such handlers with a bit of reflection:

    var genericRequestHandlers = typeof(GetAll<>).Assembly
        .ExportedTypes
        .Where(x => IsGenericRequestHandler(x))
        .ToArray();
    
    foreach (var genericRequestHandler in genericRequestHandlers)
    {
        builder
            .RegisterGeneric(genericRequestHandler)
            .AsImplementedInterfaces();
    }
    
    private static bool IsGenericRequestHandler(Type t)
    {
        return
            t.IsGenericTypeDefinition &&
            t.GetInterfaces().Any(i =>
            {
                return
                    i.IsGenericType &&
                    i.GetGenericTypeDefinition() == typeof(IRequestHandler<,>);
            });
    }
    

    Here's an explanation of the checks done in IsGenericRequestHandler:

    • is the type a generic type definition? in other words, can we construct generic types out of this type definition? GetAll<T>.Handler is since you can construct GetAll<int>.Handler, GetAll<string>.Handler, etc... out of it
    • does the type implement an interface that is a generic type? GetAll<T>.Handler implements IRequestHandler<GetAll<T>.Request, GetAll<T>.Response>, which is a generic type
    • finally, is the generic type definition of this interface IRequestHandler<,>? in our case, the generic type definition of IRequestHandler<GetAll<T>.Request, GetAll<T>.Response> is IRequestHandler<,> so the type matches the necessary criteria.

    I hope this makes sense.