I'm a relatively new user of both Autofac and ASP.NET Core. I've recently ported a small project from a 'classic' ASP.NET WebAPI project to ASP.NET Core. I am having trouble with Autofac, specifically in registration of generic types.
This project uses a Command pattern, each command handler is a closed generic like
public class UpdateCustomerCommandHandler: ICommandHandler<UpdateCustomerCommand>
These command handlers are injected into the controllers like:
readonly private ICommandHandler<UpdateCustomerCommand> _updateCustomerCommand;
public ValuesController(ICommandHandler<UpdateCustomerCommand> updateCustomerCommand)
{
_updateCustomerCommand = updateCustomerCommand;
}
Autofac is configured (partially) as:
var builder = new ContainerBuilder();
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
//This doesn't seem to be working as expected.
builder.RegisterAssemblyTypes(assemblies)
.As(t => t.GetInterfaces()
.Where(a => a.IsClosedTypeOf(typeof(ICommandHandler<>)))
.Select(a => new KeyedService("commandHandler", a)));
The above does not seem to be registering the generic as expected. If I use the below method for registration, it works well.
builder.RegisterType<UpdateCustomerCommandHandler>().As<ICommandHandler<UpdateCustomerCommand>>();
When I say "It doesn't work", what I mean is that when attempting to instantiate the controller, I get "InvalidOperationException: Unable to resolve service for type 'BusinessLogic.ICommandHandler`1[BusinessLogic.UpdateCustomerCommand]' while attempting to activate 'AutoFac_Test.Controllers.ValuesController'."
This worked well in the Full WebAPI version of this project, but not after recreating it in ASP.NET Core. To be clear, this was working perfectly well before porting to ASP.NET Core.
Here is a link to the code that I've used to recreate this issue: https://dl.dropboxusercontent.com/u/185950/AutoFac_Test.zip
**** EDIT AFTER SOLUTION DISCOVERED ****
There was nothing in fact wrong with my Autofac configuration and certainly not Autofac itself. What had happened was that I had renamed the output of my dependent assemblies in an effort to make the assembly scanning stuff (replacing of AppDomain.CurrentDomain.GetAssemblies()
more elegant, however I never modified the dependencies of the API project to reference the new assemblies. So Autofac was scanning the correctly loaded assemblies which happened to be the older versions, which did not contain the interfaces and implementations I expected...
Because Google brings you to this page even when you're trying to manually register types, I thought that even though this doesn't answer the asked question, it would be useful for future visitors. So, if you want to manually register a generic type, you would use this format:
or if there's no interface, then just:
and for completeness, if you have a generic with multiple types:
Autofac has built-in support to register closed types of open-generic.
This will scan your assembly, find types that close the open generic
ICommandHandler<>
interface, and register each of them against the closed generic interface they implement - in your case,ICommandHandler<UpdateCustomerCommand>
.What doesn't work in your example is that you associate a key to your services. Autofac doesn't look for the keyed version of your
ICommandHandler<UpdateCustomerCommand>
when trying to instantiate theValuesController
, which is why you get the exception.Edit after QuietSeditionist's comment:
I'll try to elaborate a bit on the keyed vs. default services. The way you registered your handlers is by associating the
commandHandler
key to them.This means that once the container is built, here's the only way you can resolve such a handler:
When instantiating
ValuesController
, Autofac doesn't look for a keyed registration ofICommandHandler<UpdateCustomerCommand>
, because it wasn't asked to.The equivalent code it's executing is - and you can try to run that code yourself to get the exception:
The reason your second registration works is because you didn't key the service:
But since you don't want to register all your handlers one by one, here's how to register them without keying them:
/Edit
I can see two scenarios where keying services can be useful:
You have several types implementing the same interface and you want to inject different implementations in different services. Let's say, you register both
SqlConnection
andDB2Connection
asIDbConnection
. You then have 2 services, one which is supposed to target SQL Server, the other one DB2. If they both depend onIDbConnection
, you want to make sure you inject the correct one in each service.If you use decorators, the way registrations work is you define the services to which the decorators will apply by a key - the first example is self-explanatory