Yet another homebrewed IoC/DI container
I recently had to work on a small library where I wanted to use inversion of control and dependency injection. I was faced an inevitable decision to make:
- Impose a IoC container to my users (e.g. Castle Windor, Ninject, Unity)
- Embed an IoC dll in my very little library
- Roll my own IoC container
- Not using IoC/DI
Let me tell you, none of these options are appealing. Imposing a certain container is just rude. So that was out of the question. Embedding bulky IoC containers in my little library dll was also ruled out because I don't like it. I bulks up the library unnecessarily without much benefit to the users.
I was not fond of rolling my own IoC container: It's that kind of thing you can easily get wrong because I usually use quite a few features from these containers. So I went about looking for people who might have hand rolled their own container when faced when facing similar constraints (a small library with no external dependency.)
I stumbled upon Oren Eini's (aka Ayende Rahien) take on the subject. A simple 15-line container for registering instances and getting them. The first, obvious problem is that it doesn't allow for dependency injection. And most hand rolled containers out there look mostly the same: A wrapper around a IDictionary<Type, object>
. Well, that doesn't cut if for me. Without the dependency injection part, I lose all the benefit of testability, which is why I want a IoC/DI container in the first place!
Great. Now I have to roll my own container (note the sarcasm here.)
What I really want in a IoC/DI container
I usually don't need every fancy features in modern containers. Want I really want is:
- The ability to register components
- Register Singleton and Transient components
- Providing existing instances of components
- Register components using factory methods
- Get or Create those components from the container
And above all,
- Resolving constructor dependencies
Enters Dioc
Well the name kind of sucks, but it doesn't matter. What I have now is a simple 3-file code snippet, heavily commented (sorry for those who don't like green-flooded code) with all the features listed above, and understandable exceptions when something goes wrong.
More precisely, as far as component registration is concerned, the container allows you to:
- Register interface-Implementation pairs
- Specify either Singleton or Transient
- Supports the registration of existing instances of components
- Use factory methods to create components
- Feed additional parameters on registration
The container provides the following interface for registering components:
void Singleton<T>(Hashtable parameters = null)
void Singleton<TInterface, TImplementation>(Hashtable parameters = null)
void Singleton<T>(Func<T> factoryMethod, Hashtable parameters = null)
void Transient<T>(Hashtable parameters = null)
void Transient<TInterface, TImplementation>(Hashtable parameters = null)
void Transient<T>(Func<T> factoryMethod, Hashtable parameters = null)
void Instance<T>(T instance)
void Instance<TInterface, TImplementation>(TInterface instance)
It also provides a simple interface for getting or creating those precious components. And once again, you can provide more additional parameters:
T Get<T>(Hashtable parameters = null)
object Get(Type type, Hashtable parameters = null)
Additional parameters are specified using Hashtable
s, in a key-value pair fashion. The keys have to match parameter names in eligible constructors.
Constructors, yes. Because the container also allows for constructor injection of dependencies and parameters. Using the container is pretty simple:
var container = new ComponentContainer();
container.Transient<ICustomerRepository, CustomerRepository>();
container.Singleton<ICustomerService, CustomerService>();
var service = container.Get<ICustomerService>();
And it works just as expected.
Cryptic exceptions?
Nope. In this 3-file micro-solution, there is an Exception
created specifically for the container: ComponentResolutionException
.
The container, will throw this kind of exception only (hopefully.) Plus, the container will distinguish between the following anomalies:
- An already registered component (double-registration)
- A non-registered component
- The lack of an eligible constructor
- A dependency resolution exception (exception when resolving a dependency)
- Circular dependencies
The exception sports two main properties: An Error
property, which is an enum
representing one of the five aforementioned errors. And also a RootCause
property which contains the initial error (when the error is a dependency resolution exception). The exception uses these properties to build a comprehensible error message, much like modern, full-blown containers do.
Where can I get it?
I purposefully did not create a GitHub repository for this. I used a GitHub Gist, instead. For those not accustomed with Gist, it's a version-controlled, git-backed, pastebin on steroids. In other words: I love it.
You can get the code here. It's free. It's obviously open source. And it's MIT licensed.
I created a Gist instead of a traditional repository because I want people to copy files or code into their solution, not a compiled dll. And a Gist does make that very easy.
You will also notice that the Gist actually contains four files, not three.
And it comes with tests!
I also included a very modest battery of tests. Tests are defined using NUnit but can easily be converted to fit your preferred runner. There are 20 tests at the moment and they cover most of the container's code.
Feel free to (read: do) include these in your solution too.
What it does not do
This is not a silver bullet. Modern IoC/DI containers are bulky for a reason: They do provide a lot of functionality this code excerpt does not. Worth mentioning are:
- Property injection (optional dependencies, like loggers)
- Advanced constructor resolution (this container only considers the constructor with the most parameters)
- Lazy initializing using proxies and the likes
- Much, much more
This post is way too long
Please, feel free to give it a try. And feel free to fork it on GitHub.
No new comments are allowed on this post.
Comments
No comments yet. Be the first!