Service Locator in Code?
Posted on November 28, 2007
Phil Haack posed a question today about dependency injection frameworks:
Normally, I would just specify a type to instantiate as another
PluginFamily
entry. But what I really want to happen in this case is for StructureMap to call a method or delegate and use the value returned as the constructor argument.…
Does anyone know if something like this is possible with any of the Dependency Injection frameworks out there? Whether via code or configuration?
I don’t have much experience with the big three DI frameworks Castle Windsor, Spring.NET and StructureMap, but what I’ve seen suggests that these are strongly configuration based.
That means that I can specify a concrete type name in a configuration file that the framework will use to instantiate a particular interface type. For example, in Spring.NET, you might specify the concrete type like this:
<objects xmlns="http://www.springframework.net"> <object name="TheClassIWant" type="MyNamespace.MyConcreteClass, MyAssembly"> </object> </objects>
Then in your code, you can ask Spring to create an instance of MyConcreteClass like so:
IApplicationContext ctx = ContextRegistry.GetContext(); MyConcreteClass instance = (MyConcreteClass)ctx.GetObject("TheClassIWant");
That’s all well and good. In Phil’s case, though, he wants to specify a way to create an instance of MyConcreteClass in code rather than configuration. Let’s take that requirement more generally and assert that we know what concrete classes we want to instantiate in our application, but just don’t want the whole application to know. Maybe I need an instance of ISomeInterface in some part of my code, but that code doesn’t know how to get an ISomeInterface. What I want is some global mechanism to define how to get an instance of some type, and then a way to get the type. Now, how do we do that in code?
Simple Service Locator
I had some spare time while at DevConnections, and decided to create just that – a code-based service locator. The basic idea is that we’ll have a ServiceLocator class that maps interface types to concrete types.
At some point in your code, you register a concrete type for an interface:
ServiceLocator locator = new ServiceLocator(); locator.Register(typeof(Test));
This means that when we ask for instance of the interface ITest, ServiceLocator will create and return an instance of Test (the implication of course is that Test implements ITest). Here’s how we ask for an ITest:
ITest instance = locator.Get();
If you expose a singleton instance of ServiceLocator in your application, you can register your types at startup, and then use the locator to instantiate the concrete types from anywhere in the code.
What about parameterized constructors?
Default constructors are the trivial case. How do we handle creation of concrete types that require constructor parameters? We have two cases:
- The parameter is an interface that we want ServiceLocator to instantiate
- The parameter is a value that we want to provide explicitly.
Here’s how we handle the first case:
locator.Register(typeof(Cat), typeof(IOwner)); locator.Register(typeof(Owner)); ICat instance = locator.Get();
Here we’re saying that if we want an ICat, we need to create a Cat with the constructor Cat(IOwner). By registering IOwner to create an Owner, ServiceLocator is able to create a Cat by first instantiating an Owner and passing that value to the Cat constructor.
In the second case, we have some value that ServiceLocator doesn’t know how to create. Let’s suppose the Cat constructor takes an IOwner and a string:
locator.Register(typeof(Owner)); locator.Register(typeof(Cat), new TypeParameter(typeof(IOwner)), new TypeParameter(typeof(string), "Kitty")); ICat cat = locator.Get();
In this case, ServiceLocator will call the constructor Cat(IOwner, string) with an IOwner it creates, and the string “Kitty”.
And Singletons?
Sometimes we always want the same instance of an object:
Cat kitty = new Cat(); locator.Register(kitty); ICat cat = locator.Get();
Here we always return the instance kitty when an ICat is requested. We all know how hard it is to work with a large number of cats.
Le Delegate
Yes, there is a Santa Claus. I might have some code that is responsible for creating an object. Perhaps it’s responsible for managing a pool of objects, or just has some complexity that would be difficult to express in configuration. I want to register a delegate that will provide the instance.
locator.Register(delegate() { return kitty.IsSleeping ? buffy : kitty; }); ICat cat = locator.Get();
Here I provided an anonymous method in the Register() call, but I could just as easily register some other class method.
So there you have it – a configuration-free service locator with quite a bit of flexibility in object creation.
You can download the source for my ServiceLocator here. The project was built in Visual Studio 2008.
Should I Use it?
If it works for you! It’s free of course. However, I conjured this up in an hour or two, so it’s not likely that ServiceLocator offers anywhere near the capabilities of a full-fledged dependency injection framework. I’d encourage you to look closely at Castle Windsor, Spring.NET and StructureMap. I’m sure that there are other great frameworks too.
I’d be grateful for any feedback as always.
Got something to say?