Search code examples
c#dependency-injectioninversion-of-controlsimple-injector

using simpleinjector to register opengeneric


I have the below classes:

public interface IDbCommandHandler<in TCommand, out TOutput> 
    where TCommand : IDbCommand 
{
    TOutput Handle(TCommand command);
}

public class SubIdentifierItemCreateCommand<TItemType, TDefaultValues> 
    : BaseDbCommand
    where TItemType: TDefaultValues
{

}

public class SubIdentifierItemCreateCommandHandler<TItemType, TDefaultValues>
    : BaseDbCommandHandler<SubIdentifierItemCreateCommand<TItemType, TDefaultValues>, TItemType>,
    IDbCommandHandler<SubIdentifierItemCreateCommand<TItemType, TDefaultValues>, TItemType>
    where TItemType: class, TDefaultValues, IItemForGenericItemByIdentifierRetriever , new()
{

}

I need to register the SubIdentifierItemCreateCommandHandler as singleton-open-generic, to handle any requests for services of type IDbCommandHandler<SubIdentifierItemCreateCommand<,>,>.

Is this possible? I've tried in various ways and I always get an error.

_container.RegisterSingleOpenGeneric(
    typeof(IDbCommandHandler<,>),
    typeof(SubIdentifierItemCreateCommandHandler<,>));

_container.RegisterOpenGeneric(
    typeof(IDbCommandHandler<,>),
    typeof(SubIdentifierItemCreateCommandHandler<,>));

// this one is throws a compile-time error, that you cannot 
// use partial open types.
_container.RegisterManyForOpenGeneric(
    typeof(IDbCommandHandler<SubIdentifierItemCreateCommand<,>,>),
    typeof(SubIdentifierItemCreateCommandHandler<,>)); 

I want to be able to call the below and work:

var item = _container.GetInstance<
    IDbCommandHandler<
        SubIdentifierItemCreateCommand<
            SectionData, 
            ISectionDataDefaultValues>, 
        SectionData>>();

Solution

  • Unfortunately, you stumbled upon a bug in the framework. Simple Injector 1.6.1 does not correctly handle the "where TItemType: TDefaultValues" constraint correctly.

    The solution is simple, migrate to Simple Injector 2.0 (via NuGet).

    If you for what ever reason can't switch to Simple Injector 2.0, you can register a ResolveUnregisteredType event for the registration of the type to work around the bug in the 1.6.1 release:

    container.ResolveUnregisteredType += (sender, e) =>
    {
        var serviceType = e.UnregisteredServiceType;
    
        if (serviceType.IsGenericType &&
            serviceType.GetGenericTypeDefinition() == typeof(IDbCommandHandler<,>))
        {
            var commandArg = serviceType.GetGenericArguments()[0];
            var outputArg = serviceType.GetGenericArguments()[1];
    
            if (commandArg.IsGenericType &&
                commandArg.GetGenericTypeDefinition() == 
                    typeof(SubIdentifierItemCreateCommand<,>))
            {
                var itemTypeArgument = commandArg.GetGenericArguments()[0];
                var defaultValuesArgument = commandArg.GetGenericArguments()[1];
    
                if (itemTypeArgument != outputArg)
                {
                    return;
                }
    
                Type typeToRegister;
    
                try
                {
                    typeToRegister =
                        typeof(SubIdentifierItemCreateCommandHandler<,>)
                        .MakeGenericType(itemTypeArgument.GetGenericArguments());
                }
                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;
                }
    
                object singleInstance = container.GetInstance(typeToRegister);
    
                // Register the instance as singleton.
                e.Register(() => singleInstance);
            }
        }
    };
    

    I know, it is ugly, but at least there's a work around ;-)