Dependency Injection - Part Six - Autofac's "AsImplementedInterfaces"

While I was exploring Autofac, I found AsImplementedInterfaces quite interesting. Not only it is useful, but also, if we are not careful with its use, we may end up with unexpected application behavior. However, it was quite difficult to find an example which explains -

  • What is AsImplementedInterfaces and how does it help?
  • Why does code with AsImplementedInterfaces have a bad smell?
And therefore, here we are with this post answering the above questions.

AsImplementedInterfaces

Autofac allows its users to register the types explicitly or implicitly. While "As" is used for explicit registrations, "AsImplementedInterfaces" and "AsSelf" are used for implicit ones. Please refer to the autofac docs for more details.

Consider a class BackupAndLogService, which implements two interfaces, namely BackupService and LoggingService. Another class, DataController, has a dependency on the BackupService for its data backup operations.

Now, there are two ways to register this dependency. Firstly, register BackupAndLogService explicitly as BackupService. However, if there comes another class which has a dependency of LoggingService, our code will fail. In order to meet the second requirement, we again need to register BackupAndLogService. explicitly as LoggingService.

Secondly, we can register "BackupAndLogService" with the container using AsImplementedInterfaces. As a result, the container automatically registers the implementation against all the interfaces it implements. Therefore, when we add a dependency of either of the interfaces implemented by BackupAndLogService, the container will be able to resolve it.

The Bad Smell

The problem occurs when we have two classes implementing a common interface and they both are registered using AsImplementedInterfaces. In such a case, the container will always provide the second implementation while we might be expecting the first one.

Assume, there is another class WindowsLoggingAndGeneralService, which provides (implements) LoggingService and GeneralService to windows users of the application. And, we register the two implementation classes as shown in the code below.
  1. private static void RegisterTypes(ContainerBuilder builder)   
  2. {  
  3.     builder.RegisterType < DataController > ();  
  4.     builder.RegisterType < BackupAndLogService > ().AsImplementedInterfaces();  
  5.     builder.RegisterType < WindowsLoggingAndGeneralService > ().AsImplementedInterfaces();  
  6. }  
At this point of time, if we add the dependency of LoggingService in the constructor of DataController, our application will have a different behavior for non-windows users. While resolving the LoggingService, the second registration will always win. This is because the container always considers the latest registration made against a service while resolving it.

The image shows the result of dependency resolution based on the registrations we have in the above code sample. Such situations usually occur if we are not careful with our registrations. However, in this particular scenario, we can fix the problem by registering any one of the implementations explicitly. I would leave that for you to try and observe the changes.

Well, I do agree that this might not be the best example to describe this problem but I believe this will still encourage you to be more careful with your dependency registrations.

Thank you!