Wednesday, February 17, 2010

Implementing the CommonServiceLocator on the Compact Framework

You may have come across or used the CommonServiceLocator API as developed by the p&p team at Microsoft for your desktop applications in the past.

Before we go any further, it is worth explaining what a Service Locator is. The Service Locator is a pattern that abstracts your retriveal of components or services from the underlying container model that is used to house them.

So no matter what container you use, i.e. Castle Windsor, Spring, StructureMap etc pulling anything from the container in code will always be the same and consistent across layers.

Martin Fowler explains it better than I can here: http://www.martinfowler.com/articles/injection.html

All you have to do is write an implementor for the Service Locator to use. You'll note all the famous ones on the site above have already been implemented. But of course these don't work on the CF, so what do we do?

Firstly, you need to download the source code for the CommonServiceLocator as the project needs to be modified slightly. Simply remove all references to the ActivationException.Desktop class as the constructors in that class are not supported on the CF. That is it! - for the service locator project. Now all that remains is the implementor. This depends on what container you are using. We have been using a container called CompactContainer written by Germán Schuager that works well (uses reflection as it supports true dependency injection).

CompactContainer can be downloaded from here: http://code.google.com/p/compactcontainer/ it is freely available under the Apache Licence.

In order to write an implementor for the CommonServiceLocator, all you need to do is write a class that derives from abstract class ServiceLocatorImplBase, then tell the service locator where the instance of the container is and a reference to itself. This class implements IServiceLocator too, so itself can be injected into classes - for whatever reason.

The class is really simple, it could look something like this, here we have named it CompactServiceLocator:

public class CompactServiceLocator : ServiceLocatorImplBase
{
private readonly IContainer _compactContainer;
private bool _disposed;
public CompactServiceLocator(IContainer compactContainer)
{
_compactContainer = compactContainer;
}


protected override object DoGetInstance(Type serviceType, string key)
{
object resolvedObject = null;

// Resolve using a key if we have a key
if (!key.IsNull())
{
resolvedObject = _compactContainer.Resolve(key);
}

// Resolve using type if the object has not been resolved
if(resolvedObject == null && !serviceType.IsNull())
{
resolvedObject = _compactContainer.Resolve(serviceType);
}

return resolvedObject;
}

protected override IEnumerable<object>
DoGetAllInstances(Type serviceType)
{
return _compactContainer.GetServices(serviceType);
}

private void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_compactContainer.Dispose();
}
_disposed = true;
}
}

public override void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

}
So essentially the above code is fairly simple in that all it is doing is it allows the service locator to call your specific container method. i.e. the implementation of method DoGetInstance() calls the specific container method to get an instance from the container. In this case it is method Resolve(). It is just implementation specific code which will look different from container to container.

So now you have written an implementor to go along side the Castle Windsor etc adapters, how do you use it? Well first you have to tell the Service Locator the container instance it should use, then set a reference to the service locator interface that maps to itself, like so:
Container container = new Container();
ServiceLocator.SetLocatorProvider(() => new CompactServiceLocator(container));
container.AddComponentInstance<IServiceLocator>(ServiceLocator.Current);
Container in this case is the CompactContainer freely available as mentioned above.

Asking for services is done as follows:
ServiceLocator.Current.GetInstance<IFooBar>();
Where IFooBar is your registered interface on the container.

That is it - as easy as that. If anyone would like me to put together a complete solution demoing this, please let me know. I didn't do it as the code is all available on line apart from the CompactContainer ServiceLocator implementor.

4 comments:

Daniel Marbach said...

Hy Simon,
I'm really against using Common Service Locator. For me the correct way of using an Inversion of Control container is that only a really small portion of the code should be aware of the IoC. Most of the classes should only declare their mandatory dependencies in their constructor and their non mandatory dependencies by property. I generally avoid usage of attributes etc. If one needs to dynamically create instances on request a factory should encapsulate the IoC mechanism. This makes the code clean and the DI exchangeable. Common Service Locator is a nightmare when it comes to TDD (injection of dependencies in singleton, cleanup, tests are not isolated in worst case). I would rather put effort into porting the ideas of nate's ninject etc. to compact framework. In fact there are some people around ninject community which are currently trying to port ninject latest trunk to CF.

Daniel

Simon Hart said...

Hi Daniel,

The CSL is really about abstracting the IoC container from client code where DI is not possible. For example factory classes or singletons not all classes are added to the container so can't be injected.

Yes this makes TDD slightly more difficult in that it is an extra layer of stuff to setup before testing can be done but it's not that painful. Not sure what you mean by "injection of dependenies in singleton?"

ninject is very heavyweight for device dev whic is why I've stayed away from it.

Cheers
Simon.

Daniel Marbach said...

Hy Simon
With "injection of dependenies in singleton" I meant the setup stage in your test where you need to exchange the dependencies in your service locator with mocks/stubs etc.

Daniel

Simon Hart said...

Hi Daniel,

You need to do that anyway whether you choose to use a service locator or not. This is a limitation of singleton factories - which is why people say they are hard to test. Of course not all factories are singletons!

The common service locator is just another layer to setup.

Cheers
Simon