Search code examples
c#genericsdependency-injectionsimple-injector

Get Generic Argument Type in Open Registration with Simple Injector


I am attempting to get the generic argument type in an open registration without having to use object. Here is the code snippet:

container.Register(typeof(IRepository<>),
    () => new Repository<object>("connString"),
    Lifestyle.Transient);

So in the lambda I would like to instantiate the repository with the specific generic type instead of using object.


Solution

  • The overloads of Register that take in a lambda don't support generics. The reason for this is that Simple Injector wouldn't be able to analyse the object graph for you anymore and do any analysis on your behalf.

    There are multiple solutions, but what is probably the simplest is to extract the connection string parameter into a data object, and let the repository depend on that:

    public class ConnectionStringSettings
    {
        public readonly string ConnectionString;
    
        public ConnectionStringSettings(string connectionString) {
            this.ConnectionString = connectionString;
        }
    }
    
    container.RegisterSingleton(new ConnectionStringSettings("constr"));
    container.Register(typeof(IRepository<>), typeof(Repository<>));
    

    Another option is to register the connection string directly using conditional registration:

    container.RegisterConditional(typeof(string),
        Lifestyle.Singleton.CreateRegistration(typeof(string),
            () => "constr", container),
       c => c.Consumer.ImplementationType.Name == typeof(Repository<>).Name);
    

    This way you can leave the constructor argument as is. Downside is that this registration is somewhat more complex and this conditional registration only works with reference types like strings. We might fix this in v4.

    Another option is to register all implementations explicitly:

    container.Register<IRepository<User>>(() => new Repository<User>("connString"));
    container.Register<IRepository<Order>>(() => new Repository<Order>("connString"));
    container.Register<IRepository<Asset>>(() => new Repository<Asset>("connString"));
    

    Another option is to use unregistered type resolution:

    container.ResolveUnregisteredType += (s, e) =>
    {
        Type serviceType = e.UnregisteredServiceType;
    
        if (serviceType.IsGenericType && 
            serviceType.GetGenericTypeDefinition() == typeof(IRepository<>))
        {
            Type implementationType = typeof(Repository<>)
                .MakeGenericType(serviceType.GetGenericArguments()[0]);
    
            Registration r = Lifestyle.Transient.CreateRegistration(
                serviceType,
                () => Activator.CreateInstance(implementationType, "connectionString"),
                container);
    
            e.Register(r);
        }
    };
    

    Yet another option is to override Simple Injector's default behavior when it comes to parameter injection, but this requires quite a lot of code, so this is not something I would typically advise.