Griffin.Container – “The drunken IoC container”
(I started writing this blog entry on Friday night, but somehow I didn’t manage to finish it.)
My son has just fallen asleep and wifey is working. Which means that I’ve got a Friday night all by myself. So I’ve put some beer and Bacardi Breezers (kinda girly, I know. But I can’t help myself) in a nice row on my desk. Instead of continuing to code on my next entrepreneurial endeavor I’ve decided to do something fun and challenging. And that is to code an inversion of control container in three hours (until the boss gets home). (It took another two hours today to clean up the nonsense in this blog entry)
It’s my third attempt during the last couple of years. Both of my last attempts were just a bit of fooling around and this time I’ve decided to try to create a lean killing machine. So I’ve come up with a slightly different approach compared to my last attempts.
The responsibility
In my eyes an IoC container has three responsibilities. Hence I’ve divided the container into three different classes.
Registration
A container has to be able to register the services that should be provided. I hate those XML configurations since it’s hard to discover configuration errors. I also cry when I have to use APIs which heavily use extension methods, since the intellisense for them is messy and hard to understand.
I’ve therefore created three types of registration options:
The attribute
Registration through attributes. Works quite nicely. Create a class and add the [Component] attribute on it. Repeat for all classes in all of your assemblies.
[Component]
public class MyClass : IService
{
}
Control the lifetime:
[Component(Lifetime = Lifetime.Singleton)]
public class MyClass : IService
{
}
And register everything using:
registrar.RegisterComponents(HostingEnvironment.MapPath("~/bin"), "*.dll));
Done.
The module
You can also control the registration process through modules. Great since each module/namespace/section can itself decide what should be registered in the container.
public class UserManagementModule : IContainerModule
{
public void Register(IContainerRegistrar registrar)
{
registrar.Register<UserService>();
}
}
Then just load all modules:
// can also specify path + filepattenr as the example above.
registrar.RegisterModules(Assembly.GetExecutingAssembly());
The traditional approach
There are also the traditional RegisterXxxxx methods.
public interface IContainerRegistrar
IEnumerable<ComponentRegistration> Registrations { get; }
void RegisterComponents(Lifetime defaultLifetime, string path, string filePattern);
void RegisterComponents(Lifetime defaultLifetime, params Assembly[] assemblies);
void RegisterModules(string path, string filePattern);
void RegisterModules(params Assembly[] assemblies);
void RegisterConcrete<TConcrete>(Lifetime lifetime = Lifetime.Scoped) where TConcrete : class;
void RegisterConcrete(Type concrete, Lifetime lifetime = Lifetime.Scoped);
void RegisterService<TService>(Func<IServiceLocator, object> factory, Lifetime lifetime = Lifetime.Scoped);
void RegisterService(Type service, Func<IServiceLocator, object> factory, Lifetime lifetime = Lifetime.Scoped);
void RegisterType<TConcrete, TService>(Lifetime lifetime = Lifetime.Scoped)
where TService : class
where TConcrete : class;
void RegisterType(Type concrete, Type service, Lifetime lifetime = Lifetime.Scoped);
void RegisterInstance<TService>(TService instance) where TService : class;
void RegisterInstance(Type service, object concrete);
}
The build plan
Performance is important. And you can’t get performance if you do not analyze how each service should be built (before the actual service location). I therefore go through all classes, check their dependencies and create a plan for how they should be built. I then analyze which classes are used by which services and create a mapping between each service and the implementing classes.
The builder supports multiple constructors. It uses the most specific constructor for which it can resolve all dependencies.
/// <summary>
/// Used to build the container.
/// </summary>
public interface IContainerBuilder
{
/// <summary>
/// Builds a container using the specified registrations.
/// </summary>
/// <param name="registrar">Registrations to use</param>
/// <returns>A created container.</returns>
IParentContainer Build(IContainerRegistrar registrar);
}
Service location
The container itself IS implementing the service location pattern. Hence the container interface is really a service location interface and we’ll use that as our base.
public interface IServiceLocator
{
bool IsRegistered(Type type);
T Resolve<T>() where T : class;
object Resolve(Type service);
IEnumerable<T> ResolveAll<T>() where T : class;
IEnumerable<object> ResolveAll(Type service);
}
The container itself has been divided into two containers. A parent container which is the one generated by the builder and a scoped container.
public interface IParentContainer : IServiceLocator
{
IChildContainer CreateChildContainer();
}
public interface IChildContainer : IServiceLocator, IDisposable
{
}
Scoped services can only be resolved by a scoped container. So you have to use something like this:
using (var scoped = container.CreateChildContainer())
{
// All scoped services which is resovled here
// will be disposed automatically.
}
That should make it easier to handle scoping in WCF, MVC3 and similar frameworks.
Extension points
The divided architecture allows you to customize the container quite nicely.
Custom registration
- Inherit
ContainerRegistrarand overrideAddto be able to handle all registrations. - Override
CreateRegistrationto control which interfaces a class can implement - Pass a custom
IServiceFilterclass to theContainerRegistrarto filter out which service all classes can be registered for.
Building
- Inherit
ContainerBuilderand overrideTryGetConstructorto be able to control which constructor the builder should use.
Service location
- Inherit
ContainerandChildContainerand override theGetInstancemethod to get full control over the returned instances. Perfect approach if you want to generate proxies or use AOP.
Usage
public class IntegrationTests
{
[Fact]
public void SimpleRegistrationTransient()
{
var registrar = new ContainerRegistrar();
registrar.RegisterType<MySelf>(Lifetime.Transient);
var builder = new ContainerBuilder();
var container = builder.Build(registrar);
var instance1 = container.Resolve<MySelf>();
var instance2 = container.Resolve<MySelf>();
Assert.NotSame(instance1, instance2);
}
[Fact]
public void OneDependencySingleton()
{
var registrar = new ContainerRegistrar();
registrar.RegisterType<MySelf>(Lifetime.Transient);
registrar.RegisterType<OneDepencency>(Lifetime.Singleton);
var builder = new ContainerBuilder();
var container = builder.Build(registrar);
var instance1 = container.Resolve<OneDepencency>();
var instance2 = container.Resolve<OneDepencency>();
Assert.Same(instance1, instance2);
}
[Fact]
public void ResolveAllStartable()
{
var registrar = new ContainerRegistrar();
registrar.RegisterType<Startable1>(Lifetime.Singleton);
registrar.RegisterType<Startable2>(Lifetime.Singleton);
var builder = new ContainerBuilder();
var container = builder.Build(registrar);
var instances = container.ResolveAll<ISingletonStartable>();
Assert.Equal(2, instances.Count());
}
[Fact]
public void ResolveFromComponents()
{
var registrar = new ContainerRegistrar();
registrar.RegisterComponents(Lifetime.Singleton, Assembly.GetExecutingAssembly());
var builder = new ContainerBuilder();
var container = builder.Build(registrar);
container.Resolve<OneDepencency>();
}
[Fact]
public void ResolveFromModules()
{
var registrar = new ContainerRegistrar();
registrar.RegisterModules(Assembly.GetExecutingAssembly());
var builder = new ContainerBuilder();
var container = builder.Build(registrar);
container.Resolve<MySelf>();
}
[Fact]
public void ChildContainer()
{
var registrar = new ContainerRegistrar();
registrar.RegisterType<MySelf>();
var builder = new ContainerBuilder();
var container = builder.Build(registrar);
MySelf instance;
using (var childContainer = container.CreateChildContainer())
{
instance = childContainer.Resolve<MySelf>();
}
Assert.True(instance.IsDisposed);
}
}
Performance
I used the benchmark testing code which has been created by Daniel Palme.
Name Singleton Transient Combined #.ctor Singleton #.ctor Transient #.ctor Combined
No 30 37 45 1 2000001 1000000
AutoFac 582 2635 6225 1 2000001 1000000
Dynamo 65 66 113 1 2000001 1000000
Funq 74 87 231 1 2000001 1000000
Hiro 104 99 109 1 2000001 1000000
LightInject 92 90 98 1 2000001 1000000
Munq 91 108 342 1 2000001 1000000
Petite 67 70 210 1 2000001 1000000
SimpleInjector 71 81 101 1 2000003 1000001
StructureMap 925 1032 3318 1 2000001 1000000
Unity 1346 1694 5599 1 2000001 1000000
Windsor 486 2102 6324 1 2000001 1000000
Griffin.Container 158 183 471 1 2000001 1000000
(I had to remove Ninject & Spring.NET from the chart since they are way too slow.)
The performance is OK by me since I’ve not done any optimizations yet.
Summary
I’m quite satisfied with the result and a bit surprised that I managed to create the architecture that I did last night. It was really fun and I think that I’ll continue to improve the container so it becomes even more useful.
- Code has been uploaded to github.
- MSDN style documentation