Paweł Iżycki

.NET developer, fan of automation and backend world.


Resolving after dependency injection

16 Mar 2017 » .NET, Autofac

Let’s say you have service that is responsible for creating new tenant databases. The service uses several strategies for creating database instances of different RDBMS types.

public interface ITenantDatabaseCreator {
  void Create(/* Some args */);
}

public class MssqlTenantDatabaseCreator : ITenantDatabaseCreator {
  // MS SQL implementation
}

public class OracleTenantDatabaseCreator : ITenantDatabaseCreator {
  // Oracle implementation
}

We’re good programmers so we inject our creators (strategies) with Autofac IoC container*.

This is how we can do it.

enum DatabaseType {
  Mssql, Oracle
}

public class TenantDatabaseCreateService {
  private readonly ITenantDatabaseCreator _tenantDatabaseCreator;
  
  public TenantDatabaseCreateService(ITenantDatabaseCreator tenantDatabaseCreator){
    _tenantDatabaseCreator = tenantDatabaseCreator;
  }
  
  public void CreateTenantDatabase(DatabaseType databaseType) {
    // ...
  }  
}

As we can see, implementation is trying to be resolved even before we know which one should be picked. How can we know which implementation should be taken?

There are many implementation registered to this interface, so even if we try to run this incomplete code, we’re going to receive Autofac exception.

Injecting concrete implementation in TenantDatabaseCreateService constructor is too early for us. We need some way of resolving implementation inside called method.

Interested how we can do it? There are some ways. Let’s look at each of them.

Delegate factory

This is one of the most common solutions when implementation must be resolved in method and not in constructor. It’s quite simple and if developer has some expearience with IoC containers it will be easy for him to understand it.

We register function that accepts two arguments of types IComponentContext and DatabaseType and returns seeked implementation. The function will be injected into constructor and assigned to private class member. Methods inside the class will be able to invoke the factory function and resolve implementation.

This might look a little bit like Factory pattern. For some scenarios it would be easier to use it here instead of delegate, but in result we would loose all dependency injection features.

This is how we can register delegate factory for our needs

containerBuilder.RegisterType<MssqlTenantDatabaseCreator>().AsSelf();
containerBuilder.RegisterType<OracleTenantDatabaseCreator>().AsSelf();

containerBuilder.Register<Func<DatabaseType, ITenantDatabaseCreator>>(
	c => { // c: ComponentContext, generaly use this as builded container
      switch(databaseType) {
          
        case DatabaseType.Mssql:
          return c.Resolve<MssqlTenantDatabaseCreator>();
            
        case DatabaseType.Oracle:
          return c.Resolve<OracleTenantDatabaseCreator>();
          
        default: throw new ArgumentOutOfRangeException("Unknown type.");
      }
	}); 

Using delegate factory

public class TenantDatabaseCreateService {
  private readonly Func<DatabaseType, ITenantDatabaseCreator> _createTenantDatabaseCreator;
  
  public TenantDatabaseCreateService(createTenantDatabaseCreator) {
    _createTenantDatabaseCreator = createTenantDatabaseCreator;
  }
  
  public void CreateTenantDatabase(DatabaseType databaseType) {
    var creator = _createTenantDatabaseCreator(databaseType);
    creator.Create();
    // Further instructions ...
  }
}

You may ask “What’s wrong about it?”

Resolving implementation is easy, but registering… not so much. Imagine service with hundreds of implementations. Do you really would like to generate such incredibly huge switch inside Func? Me neither.

(I know, R# can generate this switch for us, but someone has to maintain it later ;) )

Service Locator

Service Locator is considered by many as an antipattern. Ploeh has written about it quite a lot on his blog, so I won’t repeat him here. I strongly suggest reading his post.

Since it’s very common pattern (:sad:), it will be covered here as well. After all, maybe it will fit your needs? Remember, sometime square wheel work the best.

There is a service that gives us instance of container. It’s non-static injectable, but could been made static as well.

public class IocContainerProvider { // a.k.a. Service Locator
  public Autofac.IContainer GetInstance() => BuildContainer();
  
  private IContainer BuildContainer() {
    var containerBuilder = new ContainerBuilder();
    
    containerBuilder
      .RegisterType<MssqlTenantDatabaseCreator>()
      .Named<ITenantDatabaseCreator>(nameof(MssqlTenantDatabaseCreator)) // C#6 feature 
      .AsSelf();
    
    containerBuilder
      .RegisterType<OracleTenantDatabaseCreator>()
      .Named<ITenantDatabaseCreator>(nameof(OracleTenantDatabaseCreator))
      .AsSelf();
    
    return containerBuilder.Build();
  }
}

And we have our TenantDatabaseCreateService into which we inject our IocContainerProvider.

public class TenantDatabaseCreateService {
  private readonly IocContainerProvider _iocContainerProvider;
  
  public TenantDatabaseCreateService(IocContainerProvider iocContainerProvider) {
    _iocContainerProvider = iocContainerProvider;
  }
  
  public void CreateTenantDatabase(DatabaseType databaseType) {
    var creatorName = databaseType.ToString() + "TenantDatabaseCreator";
    var creator = _iocContainerProvider.GetInstance()
      				.ResolveNamed<ITenantDatabaseCreator>(creatorName);
    creator.Create();
    // Further instructions ...
  }
}

With IocContainerProvider in our class we gain access to initiate instance of ANY service registered in our IoC container. That’s probably almost every type in our system! That’s a real code smell.

With great power comes great responsibility. ~ Uncle Ben

So yeah, Service Locator will do the work, but is highly discouraged.

Resolving with an IIndex<K,V>

First of all

This is the most suitable approach for this particular case

Autofac.Features.Indexed.IIndex is Autofac feature which will provide us registered ITenantDatabaseCreator services . It acts like Dictionary. We can access our services by [index] of DatabaseType or do it in safe-way, with TryGetValue method.

So here we go. Register each implementation as ITenantDatabaseCreator with different Key .

containerBuilder.RegisterType<MssqlTenantDatabaseCreator>().Keyed<ITenantDatabaseCreator>(DatabaseType.Mssql);
containerBuilder.RegisterType<OracleTenantDatabaseCreator>().Keyed<ITenantDatabaseCreator>(DatabaseType.Oracle);

Instead of injecting instance of ITenantDatabaseCreator, we will inject Autofac.Features.Indexed.IIndex<DatabaseType, ITenantDatabaseCreator> from Autofac library.

public class TenantDatabaseCreateService {
  private readonly IIndex<DatabaseType, ITenantDatabaseCreator> _tenantDatabaseCreatorProvider;
  
  public TenantDatabaseCreateService(IIndex<DatabaseType, ITenantDatabaseCreator> tenantDatabaseCreatorProvider){
    _tenantDatabaseCreatorProvider = tenantDatabaseCreatorProvider;
  }
  
  public void CreateTenantDatabase(DatabaseType databaseType) { 
    var creator = _tenantDatabaseCreatorProvider[databaseType];
    creator.Create();
    // Further instructions ...
  }
}

Pretty neat, huh?

Let’s briefly explain what’s going on here.

We register all implementations as ITenantDatabaseCreator, each with different key. Then, we inject IIndex<DatabaseType, ITenantDatabaseCreator> to our service. It will resolve implementation basing on given DatabaseType parameter.

That’s almost like in Delegate factory. What differs? We didn’t have to write this huge switch with all of supported implementations. Autofac automatically generated this for us. This is great.

Since it works like Dictionary, only one implementation can be assigned to each DatabaseType and that’s completely fine (or even desired!).

If you would like more information, check Autofac documentation.

And don’t forget to register your services in correct scope!