Reputation: 8191
I'm using CQRS
pattern in my recent project and used Structuremap 3
as my IoC Container
, So I defined following conversion to resolve ICommandHandlers
for each BaseEntity
types:
public class InsertCommandRegistrationConvention
: StructureMap.Graph.IRegistrationConvention
{
private static readonly Type _openHandlerInterfaceType = typeof(ICommandHandler<>);
private static readonly Type _openInsertCommandType = typeof(InsertCommandParameter<>);
private static readonly Type _openInsertCommandHandlerType = typeof(InsertCommandHandler<>);
public void Process(Type type, Registry registry)
{
if (!type.IsAbstract && typeof(BaseEntity).IsAssignableFrom(type) &&
type.GetInterfaces().Any(x => x.IsGenericType &&
x.GetGenericTypeDefinition() == typeof(IAggregateRoot<>)))
{
Type closedInsertCommandType = _openInsertCommandType.MakeGenericType(type);
Type closedInsertCommandHandlerType =
_openInsertCommandHandlerType.MakeGenericType(type);
Type insertclosedHandlerInterfaceType =
_openHandlerInterfaceType.MakeGenericType(closedInsertCommandType);
registry.For(insertclosedHandlerInterfaceType)
.Use(closedInsertCommandHandlerType);
}
}
}
and used it in my CompositionRoot:
public static class ApplicationConfiguration
{
public static IContainer Initialize()
{
ObjectFactory.Initialize(x =>
{
x.Scan(s =>
{
s.TheCallingAssembly();
s.WithDefaultConventions();
s.Convention<InsertCommandRegistrationConvention>();
});
});
return ObjectFactory.Container;
}
}
so for each my entity it register appropriate InsertCommandHandler
for example it register
the InsertCommandHandler<InsertCommandParameter<Order>>
for ICommandHandler<ICommandParameter<Order>>
sometimes I need to register custom InsertCommandHandler
s for some Entities for example for Product
I want to register non-generic InsertProductCustomCommandHandler
class for ICommandHandler<ICommandParameter<Product>>
instead InsertCommandHandler<InsertCommandParameter<Product>>
(in the other word, I want to override the InsertCommendRegistrationConvention
).
How could I do this, with Structuremap 3?
Upvotes: 3
Views: 684
Reputation: 968
You can modify the convention to say something like "If there's a specific command handler, use that. Otherwise, use the InsertCommandHandler."
It would look like this (like qujck, I simplified the classes):
public class InsertCommandRegistrationConvention : IRegistrationConvention
{
private static readonly Type _openHandlerInterfaceType = typeof (ICommandHandler<>);
private static readonly Type _openInsertCommandHandlerType = typeof (InsertCommandHandler<>);
private static readonly IList<Type> _customCommandHandlerTypes;
static InsertCommandRegistrationConvention()
{
_customCommandHandlerTypes = _openInsertCommandHandlerType
.Assembly
.ExportedTypes
.Where(x => !x.IsAbstract)
.Where(x => !x.IsGenericType || x.GetGenericTypeDefinition() != typeof (InsertCommandHandler<>))
.Where(x => x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICommandHandler<>)))
.ToArray();
}
public void Process(Type type, Registry registry)
{
if (!type.IsAbstract && typeof (BaseEntity).IsAssignableFrom(type))
{
var insertclosedHandlerInterfaceType = _openHandlerInterfaceType.MakeGenericType(type);
var closedInsertCommandHandlerType = _openInsertCommandHandlerType.MakeGenericType(type);
// check for any classes that implement ICommandHandler<T> that are not also InsertCommandHandler<T>
var customHandler = _customCommandHandlerTypes.FirstOrDefault(t => t.GetInterfaces().Any(i => i == insertclosedHandlerInterfaceType));
registry.For(insertclosedHandlerInterfaceType)
.Use(customHandler ?? closedInsertCommandHandlerType);
}
}
}
The classes I used:
public abstract class BaseEntity { }
public class Class1 : BaseEntity { }
public class Class2 : BaseEntity { }
public class SpecialClass :BaseEntity { }
public interface ICommandHandler<T> { }
public class InsertCommandHandler<T> : ICommandHandler<T> { }
public class SpecialClassInsertCommandHandler : ICommandHandler<SpecialClass> { }
The tests that pass:
Assert.That(ObjectFactory.GetInstance<ICommandHandler<Class1>>(), Is.InstanceOf<InsertCommandHandler<Class1>>());
Assert.That(ObjectFactory.GetInstance<ICommandHandler<Class2>>(), Is.InstanceOf<InsertCommandHandler<Class2>>());
Assert.That(ObjectFactory.GetInstance<ICommandHandler<SpecialClass>>(), Is.InstanceOf<SpecialClassInsertCommandHandler>());
The advantage is that you modified your convention, rather than avoided using it for certain items. Which keeps everything in one spot if something about your logic has to change.
Upvotes: 2
Reputation: 14578
You can do this with the method IContainer.Configure()
- the Configure()
method allows you to add additional configuration to an existing Container
or ObjectFactory
I've simplified your abstractions to demonstrate this in action:
public abstract class BaseEntity { }
public interface ICommandHandler<T> { }
public class ClassA : BaseEntity { }
public class ClassB : BaseEntity { }
public class ClassC : BaseEntity { }
public class ClassD : BaseEntity { }
public class InsertCommandHandler<T> : ICommandHandler<T> { }
public class SpecialInsertDCommandHandler : ICommandHandler<ClassD> { }
And InsertCommandRegistrationConvention
public class InsertCommandRegistrationConvention : IRegistrationConvention
{
private static readonly Type _openHandlerInterfaceType =
typeof(ICommandHandler<>);
private static readonly Type _openInsertCommandHandlerType =
typeof(InsertCommandHandler<>);
public void Process(Type type, Registry registry)
{
if (!type.IsAbstract && typeof(BaseEntity).IsAssignableFrom(type))
{
Type closedInsertCommandHandlerType =
_openInsertCommandHandlerType.MakeGenericType(type);
Type insertclosedHandlerInterfaceType =
_openHandlerInterfaceType.MakeGenericType(type);
registry.For(insertclosedHandlerInterfaceType)
.Use(closedInsertCommandHandlerType);
}
}
}
This test demonstrates that a container configured with InsertCommandRegistrationConvention
will return the generic InsertCommandHandler<>
for all 4 of the test classes ClassA
to ClassD
[Test]
public void Handle_Initialize_RegistersClassesAToDToReturnInsertCommandHandler()
{
var container = ApplicationConfiguration.Initialize();
var resultA = container.GetInstance<ICommandHandler<ClassA>>();
var resultB = container.GetInstance<ICommandHandler<ClassB>>();
var resultC = container.GetInstance<ICommandHandler<ClassC>>();
var resultD = container.GetInstance<ICommandHandler<ClassD>>();
Assert.That(resultA.GetType() == typeof(InsertCommandHandler<ClassA>));
Assert.That(resultB.GetType() == typeof(InsertCommandHandler<ClassB>));
Assert.That(resultC.GetType() == typeof(InsertCommandHandler<ClassC>));
Assert.That(resultD.GetType() == typeof(InsertCommandHandler<ClassD>));
}
And this test shows the Configure
method successfully updates the registration for ClassD
to return SpecialInsertDCommandHandler
instead of InsertCommandHandler<ClassD>
[Test]
public void Handle_Condfigure_OverridesRegistrationForClassD()
{
var container = ApplicationConfiguration.Initialize();
container.Configure(x =>
{
x.For<ICommandHandler<ClassD>>().Use<SpecialInsertDCommandHandler>();
});
var resultD = container.GetInstance<ICommandHandler<ClassD>>();
Assert.That(resultD.GetType() == typeof(SpecialInsertDCommandHandler));
}
Upvotes: 3