Simple Service Locator
- Visit the project's home page at simpleservicelocator.codeplex.com
- Download latest release from the downloads tab at CodePlex
- Download the discussed runtime library v0.13 for .NET 3.5 - 25.28 KB (mirror)
- Download the discussed runtime library v0.13 for Silverlight - 24.41 KB (mirror)
- Download the discussed source code v0.13 - 148.53 KB (mirror)
Introduction
The Simple Service Locator is an easy-to-use Inversion of Control (IoC) library that is a complete implementation of the Common Service Locator interface. It solely supports code-based configuration, and is an ideal starting point for developers unfamiliar with larger IoC / DI libraries.
This article will describe the rational behind the Simple Service Locator, the usage and the actual implementation. This article will not explain the concept of dependency injection (DI). If you're new to the concept, there are many good articles describing DI, but Wikipedia is a good starting point.
Please note that the Simple Service Locator is a general IoC library that, despite its name, can be used for both the Service Locator and the Dependency Injection pattern.
Background
Many development teams I help have legacy code bases with few or no unit tests. As you can imagine, the complexity makes it hard to add new features and fix existing bugs. Changing the course is often not easy, because the developers need to learn a whole new bag of tricks. One of those tricks is unit testing, and to me, inextricably connected with that is Dependency Injection.
To get teams on the road quickly, I often want to do the simplest thing that could possibly work. For that reason, I've been annoyed with the complexity of the popular dependency injection frameworks. The concepts of Inversion of Control, Dependency Injection, and good RTM unit tests are by itself hard enough to grasp for many developers. Learning to work with, and configure several different frameworks (such as frameworks for presentation, logging, validation, workflow, security, O/RM, and Inversion of Control) at the same time makes it even harder.
For this reason, I started the Simple Service Locator project on CodePlex. It's yet another Inversion of Control library for .NET. The key features are its simplicity and it being an implementation of the Common Service Locator interface; an abstraction over IoC frameworks. This makes it especially useful for development teams unfamiliar with one of the existing Inversion of Control frameworks. Development teams can start using the Simple Service Locator and replace it with a more feature-rich IoC framework later on when needed, without having to alter any production code. For the lifetime of many projects however, I expect the Simple Service Locator just to be sufficient.
Usage
Simple Service Locator is an implementation of the Common Service Locator (CSL) interface, and is not meant to be used without it. Therefore, production code should only call CSL's ServiceLocator
facade, as is shown in the following example:
IWeapon weapon = ServiceLocator.Current.GetInstance<IWeapon>();
Configuring the Simple Service Locator is done in the startup path of the application, as can be seen below. In this example, you see how the Simple Service Locator is configured in the Application_Start
event of the global.asax of an ASP.NET web application.
using System;
using CuttingEdge.ServiceLocation;
using Microsoft.Practices.ServiceLocation;
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
// 1. Create a new Simple Service Locator container
var container = new SimpleServiceLocator();
// 2. Configure the container
// Register a single object instance that always
// be returned (must be thread-safe).
container.RegisterSingle<IWeapon>(new Katana());
// Register a delegate that will create a new
// instance on each call to GetInstance<IWarrior>.
container.Register<IWarrior>(() =>
new Samurai(container.GetInstance<IWeapon>());
);
// 3. Optionally, validate the configuration.
container.Validate();
// 4. Register the container to the CSL.
ServiceLocator.SetLocatorProvider(() => container);
}
}
The Simple Service Locator uses a simple form of constructor injection, which means that it can create instances for concrete types that haven't been registered manually. Look for instance at the following concrete type:
public class Samurai
{
public Samurai(IWeapon weapon, ILogger logger)
{
}
}
Because this class is concrete and has a single public
constructor, an instance can be created as follows:
ServiceLocator.Current.GetInstance<Samurai>()
Although the type doesn't need to be registered, its dependencies (IWeapon
and ILogger
) will have to be registered, simply because they are not concrete types. Their registration could look like this:
container.Register<IWeapon>(() => new Sword());
container.RegisterSingle<ILogger>(new XmlFileLoggingProvider("log.xml"));
Please note that constructor injection is only possible when a type contain a single public
constructor. Types with multiple public
constructors cannot be created. This restriction is chosen deliberately, because most IoC frameworks have different strategies. This limitation will make it easier to migrate to another framework later on.
Besides this, the Simple Service Locator has other restrictions, such as the lack of support for property injection. To be able to do property injection, IoC implementation use attributes. The use of attributes however, couples the application to a specific IoC implementation and for this reason the Simple Service Locator has no support for attribute based configuration. Note however, that in some scenarios, injection of properties is still possible by registering the proper delegates.
Besides the limit on constructor injection and property injection, there are many advanced scenarios that the Simple Service Locator does not support. While these restrictions will lead to an application with a very clean dependency inversion strategy, it can (and probably will) influence the design of your application. In case of an existing code base, it could require you to make more changes to your code than it could when using a full-fledged DI framework. This is something you might need to take into consideration.
Please visit the Simple Service Locator home page to see more code examples.
Show Me the Code, Man!
The Simple Service Locator is completely designed around .NET 2.0 generics and .NET 3.5 Func<T>
delegates. This allows type safe code-based configuration. Internally the container holds multiple dictionaries that store the registered delegates. In the next few paragraphs, I'll take you through some interesting parts of the code base. Starting at the easy stuff and ending at the slightly more advanced stuff. Let's start with the part of the SimpleServiceLocator
API that is used for setting up the container.
public class SimpleServiceLocator : ServiceLocatorImplBase
{
public void Register<T>(Func<T> instanceCreator);
public void Register<tconcrete>(Action<tconcrete> instanceInitializer);
public void RegisterByKey<T>(string key, Func<T> instanceCreator);
public void RegisterByKey<T>(Func<string, T> keyedInstanceCreator);
public void RegisterSingle<TConcrete>();
public void RegisterSingle<T>(T instance);
public void RegisterSingle<T>(Func<T> instanceCreator);
public void RegisterSingle<tconcrete>(Action<tconcrete> instanceInitializer);
public void RegisterSingleByKey<T>(string key, T instance);
public void RegisterSingleByKey<T>(string key, Func<T> instanceCreator);
public void RegisterSingleByKey<T>(Func<string, T> keyedInstanceCreator);
public void RegisterAll<T>(IEnumerable<T> collection);
public void RegisterAll<T>(params T[] collection);
public void Validate();
// Non-public methods omitted.
}
Type Registration
When we take a peek at how things are working under the covers, you'll notice that most things are actually quite simple. Let's take a look at the Register<T>(Func<T>)
method:
public void Register<T>(Func<T> instanceCreator)
{
// error checking omitted.
Func<object> objectInstanceCreator = () => instanceCreator();
this.registrations[typeof(T)] = objectInstanceCreator;
}
Did you notice that the implementation is basically a one-liner? The code simply adds the supplied Func<T>
delegate to the registrations
dictionary with the type of T
as its key. Because the registrations
dictionary accepts delegates of type Func<object>
, the instanceCreator
is 'converted' first to that type, by wrapping it in another delegate. While we could let the Register
method accept a Func<object>
delegate as argument (which would make the code slightly faster), this would make us lose type safety, a sacrifice I'm not willing to take.
A more interesting method to look at is the RegisterSingle<T>(Func<T>)
method:
public void RegisterSingle<T>(Func<T> singleInstanceCreator) where T : class
{
// error checking omitted.
Func<object> instanceCreator =
new FuncSingletonInstanceProducer<T>(singleInstanceCreator).GetInstance;
this.registrations[typeof(T)] = instanceCreator;
}
While this method doesn't look that much different from the Register<T>(Func<T>)
, the devil's in the details. This RegisterSingle
overload allows users to supply a delegate that will construct the object instance, just like with the Register
method. However, the container will ensure that the delegate will be called (at most) once during the lifetime of the application and it will ensure that the same instance is always returned. Because the container has to be thread-safe, there has to be a lock to protect this delegate from being called multiple times and the created instance must be cached. All this behavior is abstracted away in the FuncSingletonInstanceProducer<T>
helper class:
internal sealed class FuncSingletonInstanceProducer<T>
: IInstanceProducer where T : class
{
private Func<T> instanceCreator;
private bool instanceCreated;
private T instance;
internal FuncSingletonInstanceProducer(Func<T> instanceCreator)
{
this.instanceCreator = instanceCreator;
}
public object GetInstance()
{
if (!this.instanceCreated)
{
lock (this)
{
if (!this.instanceCreated)
{
this.instance = this.instanceCreator();
this.instanceCreated = true;
this.instanceCreator = null;
}
}
}
return this.instance;
}
}
The FuncSingletonInstanceProducer<T>
class wraps the Func<T>
delegate and uses a lock
to ensure the delegate is not called more than once. The class implements a double-checked lock to prevent unnecessary locking from occurring after the instance is constructed.
Also notice that the FuncSingletonInstanceProducer<T>
's GetInstance
method returns an object
. This is a very conscious design decision. Although the exact type T
is known, returning a T
would us have to create an extra wrapper delegate to convert that T
back to an object
(as we've seen with the Register
method). Thus, returning an object
will ensure the best performance and it makes the code slightly easier.
The part of the container that we've looked at is the registration part. I think you agree when I say that this isn't very spectacular. What I didn't show you are the methods that allow registration of delegates that allow returning types by a string
key. Showing you this however, wouldn't really add any value; the code looks very much like what you already saw. More interesting is the part of retrieving instances from the container. Especially because the container is able to build graphs of objects by looking at dependencies in the type's constructor, a technique which is known as constructor injection. But first things first; let's take a look at what's happening when an instance of a given type is requested.
Instance Retrieval
The SimpleServiceLocator
class overrides the protected
DoGetInstance(Type, string)
method, as it is defined by the ServiceLocatorImplBase
(which is part of the Common Service Locator library). This method delegates the processing to the private GetInstanceForType(Type)
method, that looks as follows:
private object GetInstanceForType(Type serviceType)
{
var snapshot = this.registrations;
object instance =
GetInstanceForTypeFromRegistrations(snapshot, serviceType);
if (instance != null)
{
return instance;
}
if (!IsConcreteType(serviceType))
{
// Only delegates for concrete types can be generated.
throw new ActivationException(
StringResources.NoRegistrationForTypeFound(serviceType));
}
var instanceCreator =
this.RegisterDelegateForConcreteType(serviceType, snapshot);
// Code for unregistered type resolution omitted.
return instanceCreator();
}
Some interesting things are happening here. First of all, the method makes a copy of the reference to the registrations
dictionary that holds all the Func<T>
delegates. It stores that copy in the snapshot
variable. While this seems odd and unnecessary, this actually helps in making the code thread-safe without using locks. (Bear with me, as I'll try to explain in a second.) Next, the method uses the snapshot
to load an instance by the given serviceType
. When snapshot
contains the delegate for the given serviceType
, the method will invoke that delegate and return the instance it produces. More interesting is what happens in the situation the type does not exist. There are two options here: When the serviceType
is not a concrete type and the type isn't registered, there is actually no way the container can return an instance (well... actually there is. You can do unregistered type resolution by hooking to the container's ResolveUnregisteredType
event, but that is outside the scope of this article). Instead it will throw an exception. Otherwise, when the type actually is a concrete type, the container will try to create a delegate for the construction of that type, add that delegate to the registrations
dictionary, invoke that delegate and return the constructed instance. Because that constructed delegate is cached, the next time the method is called to request an instance of that same type, the delegate will already be in the cache and can immediately be invoked, making retrieval extremely fast.
While I think most of this sounds very plausible, the trick with the reference copy might scare some of you. Why should making a copy of -only- a reference make the container thread-safe? I will try to explain.
The container is 'locked down' at the moment the first instance is requested from the container. This means that it is impossible for a user to register new types after this point. This forces users to configure the container in one place (the startup path of the application), because changing the container during the application's lifetime just isn't a wise thing to do, or it is at least a scenario that is not suited for this simple IoC library. However, it also makes it much easier to create a container that is free of locking and is therefore a lot faster.
Although the container is locked down when the first instance is requested, the dictionaries that hold the registrations can still change. The reason for this is constructor injection. As I've explained, when an unregistered concrete type is requested, the container will construct a delegate for the construction of a new instance of that concrete type by looking at its public
constructor. This delegate is cached for performance reasons, but this however, does mean the particular dictionary has to be updated.
Adding new values to that dictionary is done by replacing the old dictionary with a complete new one, thus replacing the reference. Therefore, as long as we stick to using that same reference during the execution of a single request, we can be sure that dictionary isn't going to change and we don't need a lock
. For this reason, the GetInstanceForTypeFromRegistrations
method holds on to this reference. In other words, the container is making effectively a snapshot of its state to prevent seeing any changes to the world around it!
Note that this type of reference-swapping thread-safety isn't a silver bullet; it isn't always suitable. You must be able to accept the fact that updates can get lost. Look for instance what happens when two threads request each a different unregistered concrete type at the same time. In that scenario, both threads will use the same registrations
instance. During the execution, they will each construct their own Func<T>
delegate for these concrete types. They create a copy of the registrations
dictionary and add the new delegate to their own copy. At the end of the call, both threads will override the reference to the old registrations
instance with their own copy. Since both have now their own (and different) copy, the changes of one of the threads will get lost. The important thing to note however is, while one of the threads will lose: since we're updating a single reference, we won't corrupt any state.
Why this reference-swapping technique is still suitable in this scenario however, is simple: loosing an update is not an issue, because the missing delegate will be regenerated the next time such an instance is requested. What we loose in that scenario is a bit performance, because the delegate has to be regenerated. The assumption however is, that this type of race conditions will be rare enough, that the performance impact is neglectable and finite. It is assumed to be finite, because at one time during the lifetime of the application, all configured types will be in the cache.
Constructor Injection
The last thing I like to discuss is how the Simple Service Locator is able to do constructor injection. The SimpleServiceLocator
class forwards construction of the delegates to the internal DelegateBuilder
class. This class (consisting of just 36 lines of code) is able to create a delegate that invokes the constructor of that concrete type and loads any dependencies directly from the container. For instance, let's take a look again at the concrete Samurai
class, we've seen before:
public class Samurai
{
public Sumurai(IWeapon weapon, ILogger logger)
{
}
}
The DelegateBuilder
can effectively compile a delegate that creates a new Samurai
. When we would disassemble the compiled delegate, it would look like this:
() => new Samurai(container.GetInstance<IWeapon>(), container.GetInstance<ILogger>();
Because the container is calling itself recursively, it is able to build complex graphs of objects this way. The DelegateBuilder
uses .NET 3.5's Expression
class to construct an expression and compile it down to a delegate. This is done in a few simple steps, lined out in the Build
method:
private Func<object> Build()
{
var constructor = this.GetPublicConstructor();
Expression[] constructorArgumentCalls =
this.BuildGetInstanceCallsForConstructor(constructor);
var newServiceTypeMethod = Expression.Lambda<Func<object>>(
Expression.New(constructor,
constructorArgumentCalls), new ParameterExpression[0]);
return newServiceTypeMethod.Compile();
}
The Build
method gets the public
constructor of the type. Next, the method will build a list of Expression
objects that represent the calls to container.GetInstance<T>()
; one for each constructor argument. Next, it will build a new lambda expression that expresses the intent to invoke the constructor with the supplied constructorArgumentCalls
. The last step is to compile the lambda expression to a Func<object>
delegate by calling Expression.Compile
.
Wow! Did you see how easy this became by using the new .NET Expression
API. Imagine doing this with Lightweight Code Generation using Reflection.Emit
. That would have been much more painful.
Conclusion
As you've seen, while there are a few smart design decisions made, most of the code is actually really straightforward. One of the parts I deliberately left out of the article is the error checking. Handling and communicating errors in an effective way is crucial for good usability. While it's a lot of work to get this right, I didn't think it wouldn't be a very interesting thing to show. If you are interested in this, or anything else I didn't discuss (such as unregistered type resolution), please take a look at the source code. You may reuse and abuse the code. The license permits it. If you're not using dependency injection in your application today, this is the time to start looking at it. Read more about dependency injection and try the Simple Service Locator out.
The project is currently in beta, and your feedback is very welcome. What do you think of the current API? Can we make it even simpler? Do you miss anything? Please let me know.
Happy injecting!
History
- 17th January, 2011: Text updated for v0.13
- 27th November, 2010: Text updated for v0.12
- 3rd November, 2010: Text updated for v0.11
- 25th September, 2010: Text updated for v0.10
- 9th September, 2010: Text updated for v0.9 and a thorough description of the actual implementation added
- 28th March, 2010: Text updated for v0.7
- 12th February, 2010: Text updated for v0.6
- 20th January, 2010: Updated text based on feedback from Bryan Watts
- 7th January, 2010: Initial post
发表评论
kR8X5R I really enjoy the article post.Much thanks again. Really Great.