Search code examples
genericsdependenciesregistrationcode-injection

LightInject Registration of Generic Query Handler


I am pretty new to the CQRS pattern and am having issues tying all of this together with my dependency injection container of choice.(LightInject)

What I have is a generic query object

public interface IQuery<TResult>
{
}

which is implemented by the GenericQuery

public class GenericQuery<TSrcEntity> : IQuery<IEnumerable<TSrcEntity>>
{
    public Expression<Func<TSrcEntity, bool>> Filter { get; set; }
    public Func<IQueryable<TSrcEntity>, IOrderedQueryable<TSrcEntity>> OrderBy { get; set; }
    public IEnumerable<string> IncludeProperties { get; set; }
}

I then handle all of this by way of a Query Dispatcher, which determines which handler to use through the dependency resolver

public class QueryDispatcher:IQueryDispatcher
{

    public TResult Dispatch<TQuery, TResult>(TQuery query) where TQuery : IQuery<TResult>
    {
        var handler = DependencyResolver.Get<IQueryHandler<TQuery,TResult>>();
        return handler.Retreive(query);
    }
}

the Handler Implementation

 public class GenericQueryHandler<TSrcEntity> : IQueryHandler<GenericQuery<TSrcEntity>, IEnumerable<TSrcEntity>> where TSrcEntity : class
{
    public IEnumerable<TSrcEntity> Retreive(GenericQuery<TSrcEntity> query)
    {
        return GetDocuments();
    }

My registration for LightInject looks like this

class Program
{
    static void Main(string[] args)
    {
        var container = new ServiceContainer();

        //service
        container.Register<ITestService, TestService>();

        //query
        container.Register(typeof(IQuery<>), typeof(GenericQuery<>));

        //handler This one works, but I dont want to register everything explicity.
        container.Register(typeof(IQueryHandler<GenericQuery<Document>, IEnumerable<Document>>), typeof(GenericQueryHandler<Document>));

        //dispatcher
        container.Register<IQueryDispatcher, QueryDispatcher>();

        DependencyResolver.SetResolver(container);

        var service = container.GetInstance<ITestService>();

        var a = service.GetDocuments();

    }

Everything is smooth as long as I explicitly register my handler as seen here

//handler This one works, but I dont want to register everything explicity.
        container.Register(typeof(IQueryHandler<GenericQuery<Document>, IEnumerable<Document>>), typeof(GenericQueryHandler<Document>));

But I don't want to do this for each entity. Can someone who is more familiar with LightInject help?

A sample program can be found at Generic Query Problem

Thanks


Solution

  • I had the same problem and I found a solution. I found the solution here: https://github.com/seesharper/LightInject/issues/350

    The code on that page is this:

    public static class ContainerExtensions
    {
        public static void RegisterCommandHandlers(this IServiceRegistry serviceRegistry)
        {
            var commandTypes =
                Assembly.GetCallingAssembly()
                    .GetTypes()
                    .Select(t => GetGenericInterface(t, typeof(ICommandHandler<>)))
                    .Where(m => m != null);
            RegisterHandlers(serviceRegistry, commandTypes);
            serviceRegistry.Register<ICommandExecutor>(factory => new CommandExecutor((IServiceFactory)serviceRegistry));            
            serviceRegistry.Decorate(typeof(ICommandHandler<>), typeof(TransactionalCommandDecorator<>));
        }
    
    
        public static void RegisterQueryHandlers(this IServiceRegistry serviceRegistry)
        {
            var commandTypes =
                Assembly.GetCallingAssembly()
                    .GetTypes()
                    .Select(t => GetGenericInterface(t, typeof(IQueryHandler<,>)))
                    .Where(m => m != null);
            RegisterHandlers(serviceRegistry, commandTypes);
            serviceRegistry.Register<IQueryExecutor>(factory => new QueryExecutor((IServiceFactory)serviceRegistry));
            serviceRegistry.Decorate(typeof(IQueryHandler<,>), typeof(QueryHandlerLogDecorator<,>));
        }
    
        private static Tuple<Type, Type> GetGenericInterface(Type type, Type genericTypeDefinition)
        {           
            var closedGenericInterface = 
                type.GetInterfaces()
                    .SingleOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == genericTypeDefinition);
            if (closedGenericInterface != null)
            {
                var constructor = type.GetConstructors().FirstOrDefault();
                if (constructor != null)
                {
                    var isDecorator = constructor.GetParameters().Select(p => p.ParameterType).Contains(closedGenericInterface);
                    if (!isDecorator)
                    {
                        return Tuple.Create(closedGenericInterface, type);
                    }
                }                
            }
            return null;
        }
    
        private static void RegisterHandlers(IServiceRegistry registry,  IEnumerable<Tuple<Type, Type>> handlers)
        {
            foreach (var handler in handlers)
            {
                registry.Register(handler.Item1, handler.Item2);
            }            
        }        
    }