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!
No comments:
Post a Comment