4
votes

I have the following interfaces:

public interface IQuery<TResult>
{
}

public interface IQueryHandler<in TQuery, out TResult> where TQuery : IQuery<TResult>
{
    TResult Handle(TQuery query);
}

One of my implementing queries is:

public class ApplyPermissionSetForUserAndPermissionTypeQuery<TQueryable, TEntity> : IQuery<TQueryable>
    where TQueryable : IQueryable<TEntity>
{
}

and it's handler:

public class ApplyPermissionSetForUserAndPermissionTypeHandler<TQueryable, TEntity> : IQueryHandler<ApplyPermissionSetForUserAndPermissionTypeQuery<TQueryable, TEntity>, TQueryable>
    where TQueryable : IQueryable<TEntity>
{
}

When building my container I call:

container.Register(typeof(IQueryHandler<,>), container.Settings.QueryHandlerAssemblies);

Which register all the types in the assembiles passed and has worked for all of my other handles. The problem one is different in so much that the query and handler contain a generic type themselves.

I currently have the ResolveUnregisteredType event registered on the container doing the following:

private void container_ResolveUnregisteredType(object sender, SimpleInjector.UnregisteredTypeEventArgs e)
{
    var serviceType = e.UnregisteredServiceType;

    if (serviceType.IsGenericType &&
        serviceType.GetGenericTypeDefinition() == typeof(IQueryHandler<,>))
    {
        var queryArg = serviceType.GetGenericArguments()[0];
        var resultArg = serviceType.GetGenericArguments()[1];

        if (queryArg.IsGenericType &&
            queryArg.GetGenericTypeDefinition() == typeof(ApplyPermissionSetForUserAndPermissionTypeQuery<,>))
        {
            var itemTypeArgument = queryArg.GetGenericArguments()[0];
            var entityTypeArgument = queryArg.GetGenericArguments()[1];
            if (itemTypeArgument != resultArg)
                return;

            Type typeToRegister;

            try
            {
                 typeToRegister = typeof(ApplyPermissionSetForUserAndPermissionTypeHandler<,>).MakeGenericType(itemTypeArgument, entityTypeArgument);
            }
            catch (ArgumentException)
            {
                // Thrown by MakeGenericType when the type constraints 
                // do not match. In this case, we don't have to register
                // anything and can bail out.
                return;
            }

            //Register a delegate which will return 
            e.Register(delegate () { return GetInstance(typeToRegister); });
            }
        }
    }

Which is making it work so I know it is possible to register the type. This leads me to believe I'm missing something obvious, e.g. the type is just not being registered by the "container.Register(typeof(IQueryHandler<,>), container.Settings.QueryHandlerAssemblies)" method.

Is it possible to make simple injector register this type automatically and do it in such a way that I can just register assemblies as I will not know explicit types all the time. E.g. the list of assemblies can be variable and contain unknown queries within them so I can't just make calls to:

container.Register(typeof(IQueryHandler<,>), typeof(IQueryHandler<ApplyPermissionSetForUserAndPermissionTypeQuery<,>,>))

Or will I have to enumerate the assemblies myself looking for IQueryHandler<,> types which take generic types and register them, or just change my event handler to be generic in of it's self?

1

1 Answers

3
votes

The Container.Register(Type, Assembly[]) method does not register generic registrations automatically, because you often want to handle generic implementations differently. Decorators for instance are often generic and need to be handled differently.

Since you would normally have only just a few generic implementations, it would be okay to register them explicitly as follows:

container.Register(typeof(IQueryHandler<,>), settings.QueryHandlerAssemblies);

container.Register(typeof(IQueryHandler<,>),
    typeof(ApplyPermissionSetForUserAndPermissionTypeHandler<,>));

In case you have many generic implementations and new generic implementations are added regularly, you can also include generic type definitions by calling the Container.GetTypesToRegister method first:

var queryHandlerTypes = container.GetTypesToRegister(typeof(IQueryHandler<,>), 
    settings.QueryHandlerAssemblies,
    new TypesToRegisterOptions { IncludeGenericTypeDefinitions = true });

container.Register(typeof(IQueryHandler<,>), 
    queryHandlerTypes.Where(t => !t.IsGenericTypeDefinition));

foreach (Type type in queryHandlerTypes.Where(t => t.IsGenericTypeDefinition))
{
    container.Register(typeof(IQueryHandler<,>), type);
}

Do note that this might fail directly at registration because Simple Injector might detect that the supplied open generic types overlap with previously made non-generic registrations. And even if it doesn't fail at registration time, Simple Injector might detect overlaps at runtime (or during the call to container.Verify()). That's the main reason why this Register overload does not accept batch-registration of open-generic types. If you require fallback behavior, please change the registration to:

foreach (Type type in queryHandlerTypes.Where(t => t.IsGenericTypeDefinition))
{
    container.RegisterConditional(typeof(IQueryHandler<,>), type, c => !c.Handled);
}