Thursday, February 12, 2009

On the Common Service Locator

A couple of years ago I found myself evaluating which IoC container to use in a reusable framework that we where developing at my shop.  A lot of back and forth, I recall, but I ended up choosing Castle.Windsor.

Then I thought: what if I change my mind, or what if a new and hot container hits the streets while my entire code base  is married to Windsor? I decided to implement a static class with the sole purpose of hiding the details of how the actual dependency injection was taking place from the application code (I called it ObjectFactory as a homage to StructureMap, a container which was a strong contender). Later I discovered that I had employed the Gateway Pattern.

The problem with rolling my own gateway, of course, is that I would be forever condemned to writing adapters for each new IoC framework that I would want to support. Additionally, the code base that I was working on at the time could conceivably fall into the hands of devs from other shops (it's a framework). It would be nice to be able to tell them that the code used dependency injection, but that they could choose which ever container they fancied.

Enter the Common Service Locator (released around October 2008, I believe) - CSL for short. This is a light weight framework for solving the exact problem described above. More than anything else it's a standard that the IoC vendors have agreed upon, so that they make the adapters and you don't have to. Presumably, the makers of the containers are going to do a better job than you would here, anyway. Already adapters exist for Windsor, Spring.NET, Unity, StructureMap, Autofac and even for MEF.

I urge you to download the source code. It's a pleasant experience in that you'll understand the entire code base in just a matter of minutes. It immediately becomes clear that the real value on offer here isn't the actual code, it's the fact that this is, and hopefully will remain, an "industry standard" for accessing IoC container functionality on the .net platform.

So, what's the name of the gateway in the CSL framework? It's ServiceLocator.Current, so you resolve instances doing something like this:

var service = ServiceLocator.Current.GetInstance<IMyService>();


ServiceLocator.Current? Quite a few letters, don't you think? According to ctavares (one of the coordinators of this project):



“We were following the precendent set by the .NET framework - Thread.Current, HttpContext.Current, etc.”



Fine by me, but I still don't like it, and I can't see the purpose for me in following this precedent. What you have to realize here, however, is that the thing with CSL isn't the gateway, it's the interface IServiceLocator. This is the standard that the IoC vendors implement. So, it's trivially simple to roll your own gateway that simply gives access to the instance implementing this interface.



Another thing: The standard defined by IServiceLocator exposes only the bare minimum of what a container is. Chances are that your code base is using functionality that is not defined in the interface. In my case, for instance, I need to resolve objects based on its key, i.e. I have the key, but I don't know anything about the type of either of the interface or the concrete type. This functionality is used by an engine that dynamically parses and executes code at runtime.



One more small thing: the only way to register the instance implementing IServiceLocator with ServiceLocator.Current is by feeding it a delegate of type ServiceLocatorProvider which is invoked  to produce the actual locator. Sometimes (in testing scenarios), I already have the locator. So, I expanded my static gateway with an initialization method which takes a locator directly.



I give you: IoC, my static gateway into all that IoC goodness:



/// <summary>
/// This static class implements a gateway to IoC functionality
/// through the Common Service Locator framework
/// </summary>
public static class IoC
{
private static IServiceLocator serviceLocator;
private static ServiceLocatorProvider locatorDelegate;

#region Public API - Initialization

/// <summary>
/// Sets the service locator to use internally. Any previously
/// registered ServiceLocatorProvider registered through the
/// Initialize(ServiceLocatorProvider lctrDelegate) overload
/// will be removed
/// </summary>
/// <param name="locator">The service locator to use internally</param>
public static void Initialize(IServiceLocator locator)
{
serviceLocator = locator;
locatorDelegate = null;
}

/// <summary>
/// Sets the delegate used to get the IServiceLocator used internally. This
/// delegate will be invoked for every call to the GetInstance and
/// GetAllInstances overloads. Any instance set through the
/// Initialize(IServiceLocator locator) overload is nulled, and will be
/// "lost" forever.
/// </summary>
/// <param name="lctrDelegate">The delegate to be used to provide the inner service locator</param>
public static void Initialize(ServiceLocatorProvider lctrDelegate)
{
serviceLocator = null;
locatorDelegate = lctrDelegate;
}

/// <summary>
/// Gets the instance that is registered with the given
/// key.
/// </summary>
/// <param name="key">The key.</param>
/// <returns>The object registered by the given key</returns>
public static object GetByKey(string key)
{
var locator = GetServiceLocator();

if (typeof(IExtendedServiceLocator).IsAssignableFrom(locator.GetType()))
return ((IExtendedServiceLocator) locator).GetByKey(key);

throw new NotSupportedException("Registered locator " + locator.GetType() + " doesnot implement IExtendedServiceLocator");
}

private static IServiceLocator GetServiceLocator()
{
if (serviceLocator != null) return serviceLocator;
if (locatorDelegate == null)
throw new InvalidOperationException(
"IoC must be initialized through Initialize(IServiceLocator locator) or " +
"Initialize(ServiceLocatorProvider lctrDelegate)");
return locatorDelegate.Invoke();
}

#endregion

#region Public API - ServiceLocator

/// <summary>
/// Get an instance of the given <typeparamref name="TService"/>.
/// </summary>
/// <typeparam name="TService">Type of object requested.</typeparam>
/// <exception cref="ActivationException">if there is are errors resolving
/// the service instance.</exception>
/// <returns>The requested service instance.</returns>
public static TService GetInstance<TService>()
{
return GetServiceLocator().GetInstance<TService>();
}

/// <summary>
/// Get an instance of the given <paramref name="serviceType"/>.
/// </summary>
/// <param name="serviceType">Type of object requested.</param>
/// <exception cref="ActivationException">if there is an error resolving
/// the service instance.</exception>
/// <returns>The requested service instance.</returns>
public static object GetInstance(Type serviceType)
{
return GetServiceLocator().GetInstance(serviceType);
}

/// <summary>
/// Get an instance of the given named <typeparamref name="TService"/>.
/// </summary>
/// <typeparam name="TService">Type of object requested.</typeparam>
/// <param name="key">Name the object was registered with.</param>
/// <exception cref="ActivationException">if there is are errors resolving
/// the service instance.</exception>
/// <returns>The requested service instance.</returns>
public static TService GetInstance<TService>(string key)
{
return GetServiceLocator().GetInstance<TService>(key);
}

/// <summary>
/// Get an instance of the given named <paramref name="serviceType"/>.
/// </summary>
/// <param name="serviceType">Type of object requested.</param>
/// <param name="key">Name the object was registered with.</param>
/// <exception cref="ActivationException">if there is an error resolving
/// the service instance.</exception>
/// <returns>The requested service instance.</returns>
public static object GetInstance(Type serviceType, string key)
{
return GetServiceLocator().GetInstance(serviceType, key);
}

/// <summary>
/// Get all instances of the given <typeparamref name="TService"/> currently
/// registered in the container.
/// </summary>
/// <typeparam name="TService">Type of object requested.</typeparam>
/// <exception cref="ActivationException">if there is are errors resolving
/// the service instance.</exception>
/// <returns>A sequence of instances of the requested <typeparamref name="TService"/>.</returns>
public static IEnumerable<TService> GetAllInstances<TService>()
{
return GetServiceLocator().GetAllInstances<TService>();
}

/// <summary>
/// Get all instances of the given <paramref name="serviceType"/> currently
/// registered in the container.
/// </summary>
/// <param name="serviceType">Type of object requested.</param>
/// <exception cref="ActivationException">if there is are errors resolving
/// the service instance.</exception>
/// <returns>A sequence of instances of the requested <paramref name="serviceType"/>.</returns>
public static IEnumerable<object> GetAllInstances(Type serviceType)
{
return GetServiceLocator().GetAllInstances(serviceType);
}

#endregion

}

No comments:

Post a Comment