Thursday, 27 January 2011

Breaking dependencies on specific DI containers

Warning: Before going any further you probably want to have a look at Service locator anti-pattern. The code featured in this post uses Service Locator but the pattern is regarded by some to be an anti-pattern and as such should be avoided.

I’ve been working on a WCF service that uses Unity for dependency resolution. Everything has been working but I’ve been unhappy about a tight dependency on Unity itself that I have introduced in my code. I recalled that there is a service locator library knocking around that defines an interface that IoCs can adopt.

The Common Service Locator library contains a shared interface for service location which application and framework developers can reference. The library provides an abstraction over IoC containers and service locators. Using the library allows an application to indirectly access the capabilities without relying on hard references. The hope is that using this library, third-party applications and frameworks can begin to leverage IoC/Service Location without tying themselves down to a specific implementation.” - http://commonservicelocator.codeplex.com/

The Common Service Locator library provides a simple interface for service location:

public interface IServiceLocator : IServiceProvider
{
    object GetInstance(Type serviceType);
    object GetInstance(Type serviceType, string key);
    IEnumerable<object> GetAllInstances(Type serviceType);
    TService GetInstance<TService>();
    TService GetInstance<TService>(string key);
    IEnumerable<TService> GetAllInstances<TService>();
}

It turns out that the library is supported by Unity (as well as a bunch of other IoC implementations). The Common Service Locator library site links to an adapter for Unity but peeking around I found the UnityServiceLocator class in the Microsoft.Practices.Unity assembly.

I have now been able to replace all references to IUnityContainer with IServiceLocator (i.e. breaking the tight dependency on Unity). In the case of the WCF service all I needed to do was create a ServiceHostFactory implementation that passes an instance of UnityServiceLocator around rather than an instance of UnityContainer.

public class UnityServiceLocatorServiceHostFactory : ServiceHostFactory
{
    protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
    {
        var unityContainer = new UnityContainer();
        unityContainer.LoadConfiguration();
        var unityServiceLocator = new UnityServiceLocator(unityContainer);
        return new ServiceLocatorServiceHost(serviceType, unityServiceLocator, baseAddresses);
    }  
}

The UnityServiceLocatorServiceHostFactory is the only class that has a tight dependency on Unity and can be farmed off into a separate Unity assembly. All other classes, including the service host implementation, only need to deal with IServiceLocator:

public class ServiceLocatorServiceHost : ServiceHost
{
    private IServiceLocator _serviceLocator;
        
    public ServiceLocatorServiceHost(IServiceLocator serviceLocator) : base()
    {
        _serviceLocator = serviceLocator;
    }

    public ServiceLocatorServiceHost(Type serviceType, IServiceLocator serviceLocator, params Uri[] baseAddresses)
        : base(serviceType, baseAddresses)
    {
        _serviceLocator = serviceLocator;
    }

    protected override void OnOpening()
    {
        if (Description.Behaviors.Find<ServiceLocatorServiceBehaviour>() == null)
        {
            Description.Behaviors.Add(new ServiceLocatorServiceBehaviour(_serviceLocator));
        }

        base.OnOpening();
    }
}

The ServiceLocatorServiceBehavior adds a service locator instance provider to the endpoint dispatcher, something like this:

public class ServiceLocatorServiceBehavior : IServiceBehavior
{   
    private readonly IServiceLocator _serviceLocator;
	
    public ServiceLocatorServiceBehavior(IServiceLocator serviceLocator)
    {
        _serviceLocator = serviceLocator;
    }

    public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        // Nothing to see here. Move along...
    }

    public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
    {
        // Nothing to see here. Move along...
    }

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)
        {
            foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints)
            {
                string contractName = endpointDispatcher.ContractName;
                ServiceEndpoint serviceEndpoint = serviceDescription.Endpoints.FirstOrDefault(e => e.Contract.Name == contractName);
                endpointDispatcher.DispatchRuntime.InstanceProvider = new ServiceLocatorInstanceProvider(_serviceLocator, serviceEndpoint.Contract.ContractType);
            }
        }
    }
}

And finally the ServiceLocatorInstanceProvider uses the service locator to resolve dependencies, something like this:

public class ServiceLocatorInstanceProvider : IInstanceProvider
{
    private readonly IServiceLocator _serviceLocator;
    private readonly Type _contractType;

    public ServiceLocatorInstanceProvider(IServiceLocator serviceLocator, Type contractType)
    {
        this._serviceLocator = serviceLocator;
        this._contractType = contractType;
    }

    public object GetInstance(InstanceContext instanceContext)
    {
        return GetInstance(instanceContext, null);
    }

    public object GetInstance(InstanceContext instanceContext, Message message)
    {
        return _serviceLocator.GetInstance(_contractType);
    }

    public void ReleaseInstance(InstanceContext instanceContext, object instance)
    {
            
    }
}

0 comments:

Post a Comment

By all means leave a comment. I may not be able to get back to you as quickly as I'd like but I'll do my very best.