Skip to content

AsImplementedInterfaces – code with bad smell

Last updated on April 29, 2018

While I was exploring Autofac, I found AsImplementedInterfaces quite interesting. Not only because it is useful, but also because 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 it helps?
  • why code with AsImplementedInterfaces has a bad smell?

And therefore, here we are, with this post answering the above questions.

 

What is AsImplementedInterfaces?

Autofac allows its users to register types explicitly or implicitly. While As is used for explicit registrations, AsImplementedInterfaces and AsSelf are used for implicit ones. Please refer 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 of registering 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.

private static void RegisterTypes(ContainerBuilder builder)
{
    builder.RegisterType<DataController>();
    builder.RegisterType<BackupAndLogService>().AsImplementedInterfaces();
    builder.RegisterType<WindowsLoggingAndGeneralService>().AsImplementedInterfaces();            
}

 

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.

Resolved Types for the Dependencies
Resolved Types for the Dependencies

 

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.

I agree, that this is not the best example to describe this problem. However, I believe this will yet encourage you, to be more careful with your dependency registrations.

Published inDesign Patterns & Principles