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
AsImplementedInterfacesand how it helps?
- why code with
AsImplementedInterfaceshas 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,
AsSelf are used for implicit ones. Please refer autofac docs for more details.
Consider a class
BackupAndLogService, which implements two interfaces, namely
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
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)
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)
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.
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.