Resolve IEnumerable of open generic types with Autofac

This time let's talk about Autofac, probably one of the most popular Dependency Injection container.

In the past few months, I have been working on a project in which Autofac is used to provide service instances. In some of the singleton services, I needed to access some request scoped services, so I built a fun type called IScoped<T> to be able to use per HTTP request lifetime to resolve service types. And it works. However, today, when I was building a service in which I needed a list of IScoped<T>, I ran into a problem.

One of the cool features that Autofac provides is implicit support for resolving IEnumerable<T>. Say if we register two implementation types as the same interface type, we could resolve an instance of IEnumerable of the interface type to retrieve all instances of both implementations types in one go. That's very cool, isn't it? However, it doesn't work as what I expect when resolving a list of IScoped<T>, where T has multiple implementations.

In my case, I have a service type IService, which is implemented by two other concrete types: ServiceA and ServiceB. If I resolve IEnumerable<IService>, I could get both instance of ServiceA and ServiceB in a list, which is what I want. However, when I resolve IEnumerable<IScoped<IService>>, I could see only one element in the list. Though, it makes perfect sense to me as there's only one registration for IScoped<T> (an open generic registration), I need the list to contain instances of IScoped<ServiceA> and IScoped<ServiceB>.

After researched some possible solutions, I eventually decided to build a custom registration source, for the reasons:
  • Nice and clean
  • No need to change any registration code
  • Implicit
  • Re-usable for other open generic types (as I also have other fun services)
The documentation for building a custom registration source for Autofac could be found here.

The custom registration looks like:

In the registration source, I used a DelegateActivator which on activation finds all types that implement IService interface and resolve IScoped instances of each implementation type. The list of resolved IScoped objects are later converted to IScoped<IService> instances and returned from the activator. The conversion is done using cached compiled expression trees for performance consideration.

To test the registration source, I used the following code:
The test proved the registration works. However the instances in the list are not in the same order as their registration order (In my result ServiceB appears at index of 0 whereas ServiceA is at index of 1). This might be a future thing to be addressed using the metadata of the registrations, but for now, I am happy with the result.

Comments