A couple of weeks ago I blogged about the common service locator that had just been announced by the Microsoft Patterns and Practices team. I've just updated our current application to use it. Here is what you need to do:
First, download the the binaries from codeplex. You can also get the code and build it yourself. You then need to add the Microsoft.Practices.ServiceLocation assembly to your project.
If, like me, you're using Castle Windsor you should download the code for the Castle Windsor Adaptor. This includes a file called WindsorServiceLocator.cs that you can add to your project. If you are using another IoC container, check the main page of the common service locator codeplex project for the relevant adaptor.
In your application startup code (Global.asax.cs Application_Start() for examle), register the WindsorServiceLocator with the ServiceLocator:
ServiceLocator.SetLocatorProvider(() => new WindsorServiceLocator(container));
You can now grab components from any place in your application using the ServiceLocator static Current property:
var contextService = ServiceLocator.Current.GetInstance<IContextService>();
And there's no direct dependency on Windsor so you can swap in StructureMap, AutoFac, Unity or whatever when the mood takes you.
Now there's a big big caveat with this. You should really only use the static service locator when dependency injection (DI) is not an option. In an MVC Framework application you will typically provide a ControllerFactory that gets controller instances from the IoC container. Every further dependency in the object graph that handles the request should be provided by DI. Even when you need to defer service location, the abstract factory pattern is a better choice than ServiceLocator.
The next thing that needs to happen is for ServiceLocator to be fully leveraged by the MVC Framework code. At the moment you have to use the MvcContrib WindsorControllerFactory, or write your own controller factory. It would be very nice if simply calling SetLocationProvider did the same thing.
One issue I have with the CSL interface is the fact that you set the service locator by passing a delegate or lambda. I can see the reasoning behind it but it can be a source of subtle bugs.
ReplyDeleteThe difference between
var container = new WindsorContainer(new XmlInterpreter(new ConfigResource("components")));
ServiceLocator.SetLocatorProvider(() => new WindsorServiceLocator(container));
and
ServiceLocator.SetLocatorProvider(() => new WindsorServiceLocator(new WindsorContainer(new XmlInterpreter(new ConfigResource("components"))));
is subtle, but in the second example, each call to ServiceLocator.Current news up a new container. This causes havoc, especially with services with singleton lifestyles.
As a newcomer to IoC, this is just another potential pitfall which could easily be avoided with better documentation or a better API.
Bruce,
ReplyDeleteYes, I saw your comments on this on the Codeplex site.
It's a very good point.
Looking agian at my code above, although it will not create a new container every time, it will create a new WindsorServiceLocator. That's not what I want. I should probably written this:
var serviceLocator = new WindsorServiceLocator(container);
ServiceLocator.SetLocatorProvider(() => serviceLocator);
Functional style lazy evaluation is a very powerful technique, but you are right that it doesn't make for a very obvious API. Maybe a SetLocatorProvider overload that just took a ServiceLocator is needed. What do you think?
Absolutely. They should add ServiceLocator.SetLocator(IServiceLocator).
ReplyDeleteOther than that, it's a fine API :)