Command and CommandHandler dependency Injection

687 views
Skip to first unread message

wayne.b...@gmail.com

unread,
Oct 25, 2014, 5:33:51 PM10/25/14
to aut...@googlegroups.com
Given a standard command and handler based system:

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<>));

wayne.b...@gmail.com

unread,
Oct 26, 2014, 1:20:19 AM10/26/14
to aut...@googlegroups.com, wayne.b...@gmail.com
Oh, forgot to mention something very important.

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.

Travis Illig

unread,
Oct 27, 2014, 5:54:16 PM10/27/14
to aut...@googlegroups.com, wayne.b...@gmail.com
I think what you want is something more like this:

public static class CustomRegistrationExtensions
{
// This is the important custom bit: Registering a named service during scanning.
public static IRegistrationBuilder<TLimit, TScanningActivatorData, TRegistrationStyle>
AsNamedClosedTypesOf<TLimit, TScanningActivatorData, TRegistrationStyle>(
this IRegistrationBuilder<TLimit, TScanningActivatorData, TRegistrationStyle> registration,
Type openGenericServiceType,
Func<Type, object> keyFactory)
where TScanningActivatorData : ScanningActivatorData
{
if (openGenericServiceType == null) throw new ArgumentNullException("openGenericServiceType");
return registration
.Where(candidateType => candidateType.IsClosedTypeOf(openGenericServiceType))
.As(candidateType => candidateType.GetTypesThatClose(openGenericServiceType).Select(t => (Service)new KeyedService(keyFactory(t), t)));
}
// These next two methods are basically copy/paste of some Autofac internals that
// are used to determine closed generic types during scanning.
public static IEnumerable<Type> GetTypesThatClose(this Type candidateType, Type openGenericServiceType)
{
return candidateType.GetInterfaces().Concat(TraverseAcross(candidateType, t => t.BaseType)).Where(t => t.IsClosedTypeOf(openGenericServiceType));
}
public static IEnumerable<T> TraverseAcross<T>(T first, Func<T, T> next) where T : class
{
var item = first;
while(item != null)
{
yield return item;
item = next(item);
}
}
}

Using that, you should be able to do this to register your decorator for the open generic:

var builder = new ContainerBuilder();
builder
.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.AsNamedClosedTypesOf(typeof(ICommandHandler<>), t => "implementor")
.InstancePerLifetimeScope();
builder.RegisterGenericDecorator(
typeof(TransactedCommandHandler<>),
typeof(ICommandHandler<>),
"implementor");

The challenge really was getting the scanning extensions to register a named service, which is how the decorators attach per the docs.

This somewhat overlaps existing issue #215, for which we've not yet come to a good solution.

HTH,
-T

wayne.b...@gmail.com

unread,
Oct 28, 2014, 12:35:47 AM10/28/14
to aut...@googlegroups.com, wayne.b...@gmail.com
Travis,
Wow...that is exactly what I needed for that. You are right, the challenge was to register a named service. This solved another problem where I was having trouble doing this with generics that take multiple parameters. This is something that should be included or something.

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();


Travis Illig

unread,
Oct 28, 2014, 12:39:55 PM10/28/14
to aut...@googlegroups.com, wayne.b...@gmail.com
I don't have a good solution for registering the generic decorator only for certain types. I'll leave that as "an exercise for the reader." I think you've got enough to get you unblocked. Consider grabbing the code for RegisterGenericDecorator from the Autofac source and modifying it to suit your needs in your own custom extension, the way I did for the extension I provided you.

We probably will not look to add registration of IPrincipal to the standard web types module since principal isn't a web type and folks may want to inject principals from other locations/strategies. If you are interested in having principal injected like that, it appears you've already figured out how to do it, so add that to your own standard set of registrations.

-T

wayne.b...@gmail.com

unread,
Oct 28, 2014, 7:38:23 PM10/28/14
to aut...@googlegroups.com, wayne.b...@gmail.com
Exercise for the reader...lol. Believe me I have exercised a bunch on getting this stuff to work in Autofac. I will go see what I can figure out.

Any chance for changes/enhancements coming soon in the base product to better support features like these?

Travis Illig

unread,
Oct 29, 2014, 1:05:32 PM10/29/14
to aut...@googlegroups.com, wayne.b...@gmail.com
There's a lot on the plate so I can't promise any timeline. We're working on ASP.NET vNext support, getting the documentation site updated, fixing the other issues already in the list... If I was to be honest, I'd say you are best in keeping this stuff in your own codeline for a while since the application/need is somewhat limited compared to the need for the other stuff already on the docket. Also, I've not really taken any time to consider whether offering this in general is actually a good idea or if it'd be something where people can "shoot themselves in the foot" by using it wrong or misunderstanding what it's for (which is painful for others and generates a support burden for us).

-T

Wayne Brantley

unread,
Oct 29, 2014, 7:48:09 PM10/29/14
to aut...@googlegroups.com
Sure thing - more than understandable.
Thanks for your time and efforts Travis!!!
Reply all
Reply to author
Forward
0 new messages