I'm currently evaluating Windsor (well, Ayende's Binsor interpretation of it to be more precise) to help alleviate some UI stresses in a WinForms application. Essentially I am looking for something that allows me to track a certain UI context. For example, I have broken down the application flow into a number of key contexts: Application (user login), Project, Widget. I would like each context to build upon the previously loaded contexts and when a context is ended I want to clear data associated with it. For example repositories, caches, unsaved changes, etc.
Since our project is using the Castle Project's Windsor Container it seemed like a natural place to start implementing this functionality. My first kick at this cat involved a facility that managed contexts and the data associated with each one, however, I quickly realized that my facility was mimicking the behaviour of a Lifestyle. Basically what I wanted was the ability to resolve components as per-context singletons so I decided to try and create a Custom Lifestyle to achieve it.
The first step is to create a Lifestyle Manager. I just derived a class from the MicroKernel AbstractLifestyleManager and implemented the Resolve Method:
public class ContextualLifestyleManager : AbstractLifestyleManager
{
private const string CONTEXT_ATTRIBUTE = "context";
private readonly IDictionary<Type, IContext> contextMap = new Dictionary<Type, IContext>();
private readonly IDictionary<IContext, IDictionary<Type, object>> contextualComponentMap = new Dictionary<IContext, IDictionary<Type, object>>();
public override object Resolve(CreationContext context)
{
Type contextType = RegisterApplicationContext(context);
IContext appContext = contextMap[contextType];
string serviceTypeName = context.Handler.ComponentModel.Service.FullName;
Type serviceType = Type.GetType(serviceTypeName);
if (!contextualComponentMap[appContext].ContainsKey(serviceType))
{
contextualComponentMap[appContext].Add(serviceType, base.Resolve(context));
}
return contextualComponentMap[appContext][serviceType];
}
private Type RegisterApplicationContext(CreationContext context)
{
string contextTypeName = context.Handler
.ComponentModel
.Configuration
.Attributes[CONTEXT_ATTRIBUTE];
Type contextType = Type.GetType(contextTypeName);
if (!contextMap.ContainsKey(contextType))
{
IContext appContext = (IContext) Kernel.Resolve(contextType);
appContext.Ended += HandleContextEnded;
contextMap.Add(contextType, appContext);
contextualComponentMap.Add(appContext, new Dictionary<Type, object>());
}
return contextType;
}
}
Keep in mind that you can override resolve to do whatever your heart desires... in this snip you can see I'm grabbing the name of the Service I'm requesting and also an Attribute from the ComponentModel configuration. Basically anything in the kernel is at your mercy!
Now, I also wanted to clean up after the context ends so added an event handler to respond to the IContext.Ended event:
private void HandleContextEnded(object sender, EventArgs e)
{
IContext context = (IContext) sender;
Console.WriteLine(string.Format("Removing context: {0}", context.Name));
context.Ended -= HandleContextEnded;
contextMap.Remove(context.GetType());
contextualComponentMap.Remove(context);
}
The next step was to configure this custom lifestyle in the boo container configuration. I got stuck on this for a while but eventually created a LifestyleExtension to help me out:
public class Contextual : LifestyleExtension
{
private readonly Type customLifestyleType = typeof(ContextualLifestyleManager);
public Contextual() : base(LifestyleType.Custom)
{
}
protected override void AddLifestyleDetails(IConfiguration configuration)
{
configuration.Attributes["customLifestyleType"] = string.Format("{0}, SpikeWinForms", customLifestyleType.FullName);
}
}
Here is what my Binsor config file ended up looking like (hint: @context is an attribute!):
component repository, IRepository, Repository:
lifestyle Contextual
@context = MasterContext
component applicationContext, ApplicationContext, ApplicationContext
component masterContext, MasterContext, MasterContext
Anyway, hope this post helps you out if you're trying to create custom Lifestyles using Binsor.
