How to get Simple injector to auto resolve interface when it only has one concrete?

3.3k Views Asked by At

Currently i have a webapi controller with the constructor like this:

readonly IQueryFactory queryFactory;
readonly ICommandFactory commandFactory;

public UserBenefitsController(
    IQueryFactory queryFactory, 
    ICommandFactory commandFactory)
{
    this.queryFactory = queryFactory;
    this.commandFactory = commandFactory;
}

I am using simple injector to register both of these types.

container.RegisterWebApiRequest<IQueryFactory, QueryFactory>();
container.RegisterWebApiRequest<ICommandFactory, CommandFactory>();

But i am finding that as i continue to develop my app that I am continuing to have a lot of ISomeInterface resolving to ISomeConcrete with 1:1 ratio.

Is it possible to tell simple injector to look for at an interface and automatically resolve it when there is only 1 concrete for it within the WebApiRequest scope?

2

There are 2 best solutions below

2
On BEST ANSWER

You can use batch / automatic registration to resolve to concrete instances. The Simple Injector documentation explains it here

For instance:

ScopedLifestyle scopedLifestyle = new WebApiRequestLifestyle();

var assembly = typeof(YourRepository).Assembly;

var registrations =
    from type in assembly.GetExportedTypes()
    where type.Namespace == "Acme.YourRepositories"
    where type.GetInterfaces().Any()
    select new
    {
        Service = type.GetInterfaces().Single(),
        Implementation = type
    };

foreach (var reg in registrations)
    container.Register(reg.Service, reg.Implementation, scopedLifestyle);
1
On

Besides the correct (and probably preferable) answer of Mr Bacardi, you can also achieve this using unregistered type resolution, using the following extension method:

public static void AutoMap(this Container container, params Assembly[] assemblies) {
    container.ResolveUnregisteredType += (s, e) => {
        if (e.UnregisteredServiceType.IsInterface && !e.Handled) {
            Type[] concreteTypes = (
                from assembly in assemblies
                from type in assembly.GetTypes()
                where !type.IsAbstract && !type.IsGenericType
                where e.UnregisteredServiceType.IsAssignableFrom(type)
                select type)
                .ToArray();

            if (concreteTypes.Length == 1) {
                e.Register(Lifestyle.Transient.CreateRegistration(concreteTypes[0], 
                    container));
            }
        }
    };
}

You can use this extension method as follows:

container.AutoMap(Assembly.GetExecutingAssembly());

It is important that you apply a strict set of assemblies to this method, containing only the production assemblies. When you apply all AppDomain assemblies, you'll find that it is likely to fail in your integration or unit test suite, since you will likely have fake implementations of those interfaces in those assemblies.

My preference though is to do as much explicit up front registration as possible, which is the solution that Mr Bacardi describes. The method described here uses a 'just in time' approach, where it starts to look for an implementation on the first resolve. If you call container.Verify() however, you will be safe in most cases, although it is possible that a call to Verify() succeeds, while resolving object graphs fails as runtime, because Verify() will only verify objects that are known to the configuration. So you will have to make sure that your unregistered interfaces are directly or indirectly referenced by something that actually is registered. Since it is easy to overlook such thing, I prefer Mr Barardi's approach.

But I must admit that in the applications I work on (which are usually quite big), we have just a few of those 'one-to-one' implementations. Most implementations in the system implement a generic interface (such as this and this) and there's hardly ever a reason for me to do batch registration on those non-generic interfaces. Simple Injector on the other hand has great support for batch registering generic interfaces using the Register methods.