public interface ICommand {}
public interface ICommandHandler<in TCommand> where TCommand: class, ICommand
{
void Handle(Tcommand command);
}
We then declare classes:
public class ChargeCreditCardCommand : ICommand
{
public string CreditCardNumber: {get;set;}
}
public class ChargeCreditCardHandler: ICommandHandler<ChangePasswordCommand>
{
public void Handle(ChangePasswordCommand command)
{
....do work....
}
}
At some point I would have a class that uses the above..
public class MyController:Controller{
{
public MyController( ICommandHandler<ChargeCreditCardCommand> chargeCommand){}
}
Now to register these up so they can be resolved by Autofac.
//This following works, but is completely manual for each type and handler..
builder.RegisterAssemblyTypes(Assembly.GetAssembly(Assembly.GetExecutingAssembly()))
.AsClosedTypesOf(typeof(ICommandHandler<>))
.InstancePerLifetimeScope();
builder.RegisterType<ChargeCreditCardCommand>()
.Named<ICommandHandler<ChargeCreditCardCommand>>("TestHandler")
.InstancePerLifetimeScope();
I then wrote an Autofac extension to handle this and you use it like this:
builder.RegisterAssemblyTypesWithMapHandler(typeof(ICommand), typeof(ICommandHandler<>), Assembly.GetAssembly(Assembly.GetExecutingAssembly()));
Here is the class...
public static class AutofacExtension
{
public static void RegisterAssemblyTypesWithMapHandler(this ContainerBuilder builder, Type item, Type handler, params Assembly[] assemblies)
{
var mainTypeList = assemblies.SelectMany(a => a.GetTypes()).ToList();
var itemTypes = mainTypeList.Where(t => !t.IsInterface && !t.IsAbstract && item.IsAssignableFrom(t)).ToList();
foreach (var itemType in itemTypes)
{
var genericHandler = handler.MakeGenericType(itemType);
var handlerTypes = mainTypeList.Where(t => !t.IsInterface && !t.IsAbstract && genericHandler.IsAssignableFrom(t)).ToList();
foreach (var handlerType in handlerTypes)
builder.RegisterType(handlerType).As(genericHandler).InstancePerLifetimeScope();
}
}
}
I then saw that SimpleInjector does this same work without requiring the item, must figure it out from generic arguments...so I attempted to write that and it did not work.
Pretty sure I did not handle the argument right and did not know how to handle multiple arguments...
//figure out typed argument for generic
public static void RegisterAssemblyTypesWithMapHandler(this ContainerBuilder builder, Type handler, params Assembly[] assemblies)
{
var item = handler.GetGenericArguments().First();
var mainTypeList = assemblies.SelectMany(a => a.GetTypes()).ToList();
var itemTypes = mainTypeList.Where(t => !t.IsInterface && !t.IsAbstract && item.IsAssignableFrom(t)).ToList();
foreach (var itemType in itemTypes)
{
var genericHandler = handler.MakeGenericType(itemType);
var handlerTypes = mainTypeList.Where(t => !t.IsInterface && !t.IsAbstract && genericHandler.IsAssignableFrom(t)).ToList();
foreach (var handlerType in handlerTypes)
builder.RegisterType(handlerType).As(genericHandler).InstancePerLifetimeScope();
}
}
1) Is this the best way to do something like this?
2) You can see the InstancePerLifetimeScope() call is in the RegisterAssemblyTypesWithMapHandler and that is not ideal.
I really wanted to return an IRegistrationBuilder (but could not get it to work) so I could do this:
builder.RegisterAssemblyTypesWithMapHandler(typeof(ICommand), typeof(ICommandHandler<>), Assembly.GetAssembly(Assembly.GetExecutingAssembly()))
.InstancePerLifetimeScope();
3) The above extension, is this something you would consider including in Autofac?
4) Once the above is done, will I be able to use the built in Decorator pattern on these registrations?
Essentially with the 'SimpleInjector' product what I want to happen is done with these lines of code and I wanted something as simple for Autofac.
//this line does what my extension above does.
container.RegisterManyForOpenGeneric(typeof(ICommandHandler<>),
AppDomain.CurrentDomain.GetAssemblies());
// Decorate each returned ICommandHandler<T> object with
// a TransactionCommandHandlerDecorator<T>.
container.RegisterDecorator(typeof(ICommandHandler<>),
typeof(TransactionCommandHandlerDecorator<>));
builder.RegisterAssemblyTypes(Assembly.GetAssembly(Assembly.GetExecutingAssembly()))
.AsClosedTypesOf(typeof(ICommandHandler<>))
.AsImplementedInterfaces();
Will properly register everything. However, you cannot use the decorator pattern on this. Instead you have to use my extension, using the Name() instead of the As() call when registering. Then you can use that name in your RegisterGenericDecorator() call later.
It seems other products do not need this 'key' to do this same work and I think that complicates things (perhaps unnecessarily), but may due to a different way things are stored/accessed/lookedup behind the scenes? Example:
https://simpleinjector.readthedocs.org/en/latest/advanced.html#decorators
I have grown use to autofac and come to really rely on it, so I do not want to change - but seeing how easy it is to do in the other products make me think about it for a second! :-)
On your command, can I filter what it is applied to? As an example, let's say that I want the TransactedCommandHandler to only be a decorator to ones that implement the ITransactionRequired interface on the command. (In addition to the ICommand of course).
Ideally I would RegisterGenericDecorator only for those instances?
var builder = new ContainerBuilder();
builder
.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.AsNamedClosedTypesOf(typeof(ICommandHandler<>), t => "implementor")
.InstancePerLifetimeScope();
builder.RegisterGenericDecorator(
typeof(TransactedCommandHandler<>),
typeof(ICommandHandler<>),
"implementor");
maybe something like:
builder.RegisterGenericDecorator(
typeof(TransactedCommandHandler<>),
typeof(ICommandHandler<>),
"implementor", c => c.ImplementsInterface<ITransactionRequired>);
Again, what you provided is absolutely PERFECT for what I asked and I bet this helps others trying to do this same thing - thanks so much.
BTW, on a completely unrelated note - the AutofacWebTypesModule(), should that consider doing an automatic registration of IPrincipal?:
builder.Register(c => c.Resolve<HttpContextBase>().User).As<IPrincipal>().InstancePerRequest();