Caliburn.Micro ships with an aptly named basic inversion of control container called SimpleContainer. The container satisfies most scenarios, but I’ve discovered a few minor concerns when registering classes that support more than one interface.
Suppose I have a class that implements two interfaces: IApplicationService and IMetricsProvider:
public class MetricsService : IApplicationService, IMetricsProvider
{
#region IApplicationService
public void Initialize()
{
// initialize metrics...
}
#endregion
#region IMetricsProvider
public void IncrementMetric(string metricName)
{
// do something with metrics...
}
#endregion
}
The IApplicationService is a pattern I usually implement where I want to configure a bunch of background services during application startup, and the IMetricsProvider is a class that will be consumed elsewhere in the system. It's not a perfect example, but it'll do for our conversation...
The SimpleContainer implementation doesn't have a good way of registering this class twice without registering them as separate instances. I really want the same instance to be used for both of these interfaces. Typically, to work around this issue, I might do something like this:
var container = new SimpleContainer(); container.Singleton<IMetricsProvider,MetricsService>(); var metrics = container.GetInstance<IMetricsProvider>(); container.Instance<IApplicationService>(metrics);
This isn't ideal though it will work in trivial examples. Unfortunately, this approach can fail if the class has additional constructor dependencies. In that scenario, the order in which I register and resolve dependencies becomes critical. If you resolve in the wrong order, the container injects null instances.
To work around this issue, here's a simple extension method:
public static class SimpleContainerExtensions
{
public static SimpleContainerRegistration RegisterSingleton<TImplementation>(this SimpleContainer container, string key = null)
{
container.Singleton<TImplementation>(key);
return new SimpleContainerRegistration(container, typeof(TImplementation), key);
}
class SimpleContainerRegistration
{
private readonly SimpleContainer _container;
private readonly Type _implementationType;
private readonly string _key;
public SimpleContainerRegistration(SimpleContainer container, Type type, string key)
{
_container = container;
_implementationType = type;
_key = key;
}
public SimpleContainerRegistration AlsoAs<TInterface>()
{
container.RegisterHandler(typeof(TInterface), key, container => container.GetInstance(_implementationType, _key));
return this;
}
}
}
This registers the class as a singleton and allows me to chain additional handlers for each required interface. Like so:
var container = new SimpleContainer();
container.RegisterSingleton<MetricsService>()
.AlsoAs<IApplicationService>()
.AlsoAs<IMetricsProvider>();
Happy coding!
0 comments:
Post a Comment