Integrating FubuMVC with ASP.NET MVC, Part 2
August 13th 2010As mentioned in the previousPost the default IoC container for FubuMVC is StructureMap. The website I wanted to integrate the Fubu behavior chains into was currently using Windsor so I set out to try and come up with something that could provide equivalent functionality for what needed to be done.
Picking back up at the FubuBootstrapper class in the Bootstrap method:
// "Bake" the fubu configuration model into your
// IoC container for the application
BehaviorGraph graph = _topRegistry.BuildGraph();
graph.EachService(_facility.Register);
IBehaviorFactory factory = _facility.BuildFactory();
Remember that the BuildGraph() call is where it builds up your behavior chains with all the conventions you have specified, after which the fully configured IActionBehavior with all dependencies is registered into your IoC container under a specific key to be resolved later when needed.
Let’s look at a few important things that make that happen.
Inside the graph.EachService you will find this:
_behaviors.Each(chain => { action(typeof (IActionBehavior), chain.ToObjectDef()); });
Which turns each of your behaviors into the format necessary to be passed
through the facility.Register method, which is where things actually get
registered into your container. It’s signature looks like this:
public void Register(Type serviceType, ObjectDef def)
So the serviceType is IActionBehavior, and the ObjectDef was built by calling
chain.ToObjectDef(). If you recall from the previous post I already had to
change ToObjectDef on the BehaviorChain class slightly to provide a different
name, as FubuMVC normally uses a UniqueId GUID but I changed it to
FirstCallDescription.ToLower() to provide myself something that I could match
up from within the custom controller action invoker when it was invoking a
controller action in order to identify the matching behavior chain from the
container. Here is the changed BehaviorChain ToObjectDef implementation again:
public override ObjectDef ToObjectDef()
{
ObjectDef def = Top.ToObjectDef();
def.Name = FirstCallDescription.ToLower();
return def;
}
This time lets look into what the ObjectDef is, how its built, and why it matters.
If you follow Top.ToObjectDef you’ll find your way to this code inside of
BehaviorNode:
public virtual ObjectDef ToObjectDef()
{
ObjectDef objectDef = toObjectDef();
objectDef.Name = UniqueId.ToString();
return objectDef;
}
protected ObjectDef toObjectDef()
{
ObjectDef objectDef = buildObjectDef();
if (Next != null)
{
var dependency = new ConfiguredDependency
{
Definition = Next.ToObjectDef(),
DependencyType = typeof (IActionBehavior)
};
objectDef.Dependencies.Add(dependency);
}
return objectDef;
}
protected abstract ObjectDef buildObjectDef();
Looking at the toObjectDef() call you can see that the Next behavior in the
chain adds itself into the current object def instance as a
ConfiguredDependency which enables it to instruct the IoC container exactly
what is needed to fulfill its dependencies when it is resolved later.
Alternatively if you want to provide an actual object instance as a dependency
you can use ValueDependency.
Now back to the actual implementation of the Register method from the
facility, it looks like this:
public void Register(Type serviceType, ObjectDef def)
{
if (def.Value == null)
{
_registry.For(serviceType).Add(new ObjectDefInstance(def));
}
else
{
_registry.For(serviceType).Add(new ObjectInstance(def.Value));
}
if (ServiceRegistry.ShouldBeSingleton(serviceType))
{
_registry.For(serviceType).Singleton();
}
}
Our behavior chain registration comes through the first if case, as we are not
providing actual object instances via def.Value and are instead specifying
them through a hierarchy of configured dependencies. So we are most concerned
with this line:
_registry.For(serviceType).Add(new ObjectDefInstance(def));
What is an ObjectDefInstance?
public class ObjectDefInstance : ConfiguredInstance, IDependencyVisitor
{
public ObjectDefInstance(ObjectDef definition)
: base(definition.Type)
{
definition.AcceptVisitor(this);
Name = definition.Name;
}
void IDependencyVisitor.Value(ValueDependency dependency)
{
Child(dependency.DependencyType).Is(dependency.Value);
}
void IDependencyVisitor.Configured(ConfiguredDependency dependency)
{
var child = new ObjectDefInstance(dependency.Definition);
Child(dependency.DependencyType).Is(child);
}
}
It inherits from ConfiguredInstance which belongs to StructureMap and its purpose serves as a way to control at a very granular level exactly what is to be provided as each dependency is requested.
I had seen enough now to start thinking about how to get Windsor in on the action. I wanted to mimic the existing structure as closely as possible so I made a WindsorContainerFacility to match the StructureMapContainerFacililty.
It seemed the Windsor equivalent for taking control over how to specify dependencies was to implement my own CustomComponentActivator.
Its worth noting that StructureMap uses a NestedContainer feature to handle scoping transient instances it resolves to the current web request. In Windsor the equivalent seemed to be the LifeStyle.PerWebRequest option.
First I had to modify the ObjectDefInstance to inherit from my own version of ConfiguredInstance rather than the StructureMap one, here it is:
public class ConfiguredInstance
{
private readonly IDictionary<Type, ChildExpression> _dependencies = new Dictionary<Type, ChildExpression>();
private readonly Type _pluggedType;
public ConfiguredInstance(Type pluggedType)
{
_pluggedType = pluggedType;
}
public IDictionary<Type, ChildExpression> Dependencies
{
get { return _dependencies; }
}
public string Name { get; set; }
public Type PluggedType
{
get { return _pluggedType; }
}
public ChildExpression Child(Type serviceType)
{
var dependency = new ChildExpression(serviceType);
_dependencies.Fill(serviceType, dependency);
return dependency;
}
}
With its accompanying ChildExpression class:
public class ChildExpression
{
private readonly Type _serviceType;
private ObjectDefInstance _objectDefInstance;
private object _value;
public ChildExpression(Type serviceType)
{
_serviceType = serviceType;
}
public Type ServiceType
{
get { return _serviceType; }
}
public void Is(object value)
{
_value = value;
}
public void Is(ObjectDefInstance objectDefInstance)
{
_objectDefInstance = objectDefInstance;
}
public object GetValue(IKernel kernel, IDictionary additionalParameters)
{
if (_value != null)
{
return _value;
}
var key = _objectDefInstance.Name;
if (!kernel.HasComponent(key))
{
kernel.Register(_serviceType, _objectDefInstance);
}
return kernel.Resolve(key, _serviceType, additionalParameters);
}
}
Some IoC containers let you resolve types that have not been registered already. It doesn’t seem that Windsor is one of them, so I had to slip in that piece:
if (!kernel.HasComponent(key))
{
kernel.Register(_serviceType, _objectDefInstance);
}
To quickly register it last minute if necessary. That GetValue method is used
from within the CustomComponentActivator I came up with, which we will inspect
more closely soon.
Here’s the Windsor facility:
public class WindsorContainerFacility : IContainerFacility, IBehaviorFactory
{
private readonly IWindsorContainer _container;
public WindsorContainerFacility(IWindsorContainer container)
{
_container = container;
}
public IBehaviorFactory BuildFactory()
{
_container.Register(Component.For<IBehaviorFactory>().ImplementedBy<WindsorContainerFacility>());
var registration = Component.For<IMvcAction>().ImplementedBy<MvcAction>();
registration = HttpRuntime.AppDomainAppVirtualPath != null
? registration.LifeStyle.PerWebRequest
: registration.LifeStyle.Singleton;
_container.Register(registration);
return this;
}
public void Register(Type serviceType, ObjectDef def)
{
if (def.Value == null)
{
_container.Kernel.Register(serviceType, new ObjectDefInstance(def));
}
else
{
_container.Kernel.Register(Component.For(serviceType).Instance(def.Value));
}
}
public IActionBehavior BuildBehavior(Guid behaviorId)
{
return _container.Resolve<IActionBehavior>(behaviorId.ToString());
}
}
To keep the feel of the StructureMap facility I made a Register extension method to be used on the Windsor IKernel that mirrors the syntax of the original StructureMapContainerFacility’s Register method.
public static class WindsorExtensions
{
public static void Register(this IKernel kernel, Type serviceType, ConfiguredInstance configuredInstance)
{
IDictionary extendedProperties = new Dictionary<string, object>
{
{"configuredInstance", configuredInstance}
};
ComponentModel model = kernel.ComponentModelBuilder.BuildModel(
configuredInstance.Name,
serviceType,
configuredInstance.PluggedType,
extendedProperties
);
model.LifestyleType = HttpRuntime.AppDomainAppVirtualPath != null
? LifestyleType.PerWebRequest
: LifestyleType.Transient;
model.CustomComponentActivator = typeof(ConfiguredInstanceActivator);
model.InspectionBehavior = PropertiesInspectionBehavior.All;
((IKernelInternal)kernel).AddCustomComponent(model);
}
}
I ended up having to pull down the source to Windsor to look into how to accomplish a few of the things I needed done. The above is the result of my efforts.
The CustomComponentActivator I use is called ConfiguredInstanceActivator and is based on the original source from Windsors own DefaultComponentActivator but is trimmed down to exclude things I don’t explicitly use and with a few key changes to handle what I needed. I will point out the differences.
In the beginning of the Register method you see the extendedProperties I provide contains the ConfiguredInstance that came from the ObjectDef as follows:
IDictionary extendedProperties = new Dictionary<string, object>
{
{"configuredInstance", configuredInstance}
};
And then inside the ConfiguredInstanceActivator I pull that back out and set all of the values contained within into the CreationContext which is what Windsor uses to figure out how to resolve what you want.
protected override object InternalCreate(CreationContext context)
{
var configuredInstance = Model.ExtendedProperties["configuredInstance"] as ConfiguredInstance;
if (configuredInstance == null)
throw new InvalidOperationException("configuredInstance was not defined in ExtendedProperties");
var reflection = Kernel.Resolve<IReflection>();
//null out converter on creation context to enable it to offer up additional parameters as candidates
//for dependency resolution
reflection.SetConverterFieldToNull(context);
configuredInstance.Dependencies.Each(
x => { context.AdditionalParameters[x.Key] = x.Value.GetValue(Kernel, context.AdditionalParameters); });
object instance = Instantiate(context);
SetUpProperties(instance, context);
return instance;
}
CreationContext exposes its additionalArguments via the AdditionalParameters property, so I copy the contents of the configured instance dependencies into it. However I ran into a bit of a snag.
Inside the Resolve method on the CreationContext there was one pesky thing that
stopped me from getting where I needed to be. I had already provided the
arguments I wished to be used via the additionalArguments but there is a
private ITypeConverter converter field that is provided by default and the
following code that barred my way:
var inlineArgument = additionalArguments[dependency.DependencyKey];
if (inlineArgument != null)
{
if (converter != null &&
!dependency.TargetType.IsInstanceOfType(inlineArgument) &&
dependency.DependencyType == DependencyType.Parameter)
{
return converter.PerformConversion(inlineArgument.ToString(), dependency.TargetType);
}
return inlineArgument;
}
What I needed was for it to simply return the argument as I had specified it from the additionArguments dictionary. However as this converter existed it would instead ToString the argument and run conversion on it. So I resorted to using reflection to null it out. There’s probably another way to do this via overriding the RegisterSubSystems call on the DefaultKernel and not adding the DefaultConversionManager which is by default provided as the ITypeConverter to CreationContext when it is made, but for my purposes this worked and is contained within my custom component activator. I also cache the reflection call to alleviate my conscience so I sleep better at night.
var reflection = Kernel.Resolve<IReflection>();
//null out converter on creation context to enable it to offer up additional parameters as candidates
//for dependency resolution
reflection.SetConverterFieldToNull(context);
The useful pieces of the IReflection implementation are as follows:
private readonly FieldInfo _creationContextConverterField;
_creationContextConverterField = typeof(CreationContext).GetField(
"converter",
BindingFlags.Instance | BindingFlags.NonPublic);
public void SetConverterFieldToNull(object instance)
{
_creationContextConverterField.SetValue(instance, null);
}
So finally it was using the arguments I wanted it to, but I soon ran across another problem. Windsor looks in a lot of places to try and resolve your dependencies if it can’t find it outright it will fall back on pretty much anything it can find that matches your type, which in some cases was an entire other BehaviorChains IActionBehavior that had also been registered into the container.
I had to add a piece into the SetUpPropeties on the ConfiguredInstanceActivator to tell it to only check the CreationContext for IActionBehavior dependencies and not to fall back on the Kernel.Resolver and other sub resolvers.
protected virtual void SetUpProperties(object instance, CreationContext context)
{
foreach (PropertySet property in Model.Properties)
{
object value = null;
//IActionBehaviors are only allowed to come off the creation context, don't want to resolve any through default mechanism
if (property.Dependency.TargetType == typeof (IActionBehavior))
{
if (context.CanResolve(context, context.Handler, Model, property.Dependency))
{
value = context.Resolve(context, context.Handler, Model, property.Dependency);
}
}
else if (Kernel.Resolver.CanResolve(context, context.Handler, Model, property.Dependency))
{
value = Kernel.Resolver.Resolve(context, context.Handler, Model, property.Dependency);
}
if (value == null) continue;
MethodInfo setMethod = property.Property.GetSetMethod();
try
{
setMethod.Invoke(instance, new[] {value});
}
catch (Exception ex)
{
String message =
String.Format(
"Error setting property {0} on type {1}, Component id is {2}. See inner exception for more information.",
setMethod.Name, instance.GetType().FullName, Model.Name);
throw new ComponentActivatorException(message, ex);
}
}
}
That’s pretty much it. It was rewarding to finally get all of this to actually work and we are now using it on our live site. Thanks to the authors of FubuMVC for providing an awesome code base to look through and learn from. I will post a trimmed down sample project soon that contains all of this that can be looked at in more detail.