There are plenty of good Dependency Injection Container implementations out there. The selection of an IoC container is not only defined by the developer's preference, which can swing the vote big time since most developers swear by their favorite container, but also by other factors like standards in the organization, the usage of another product which already supports or favors a given IoC implementation, etc.
The usage of an IoC container should always be abstracted, making the process of switching between implementations fairly simple, or at least, the refactoring needed to accomplish this task will be minimal compared to the one needed if you were using references to a specific container all over your codebase.
My abstraction layer contains 3 main components:
- An interface to define IoC container usage.
- Classes implementing the interface for each IoC container to support.
- A singleton to allow to "resolve" types using the "current" IoC container.
Here is the code implementing these 3 components.
1- IDependencyInjectionContainer interface. From a client perspective, an IoC container allows to resolve type or types in a dynamic way. This interface outlines the most common ways to resolve types.
1: public interface IDependencyInjectionContainer
2: {
3: T Resolve<T>();
4: T Resolve<T>(string name);
5: IEnumerable<T> ResolveAll<T>();
6: T Resolve<T>(T existing);
7: T Resolve<T>(T existing, string name);
8: }
2- IDependencyInjectionContainer implementation. I've chosen Unity for this article since is the one I used on my last project.
1: public class UnityDependencyInjectionContainer : IDependencyInjectionContainer
2: {
3: private readonly IUnityContainer container;
4:
5: public UnityDependencyInjectionContainer(IUnityContainer c)
6: {
7: this.container = c;
8: }
9:
10: public T Resolve<T>()
11: {
12: return this.container.Resolve<T>();
13: }
14:
15: public T Resolve<T>(string name)
16: {
17: return this.container.Resolve<T>(name);
18: }
19:
20: public IEnumerable<T> ResolveAll<T>()
21: {
22: return this.container.ResolveAll<T>();
23: }
24:
25: public T Resolve<T>(T existing)
26: {
27: return this.container.BuildUp(existing);
28: }
29:
30: public T Resolve<T>(T existing, string name)
31: {
32: return this.container.BuildUp(existing, name);
33: }
34: }
3. TypeFactory is a singleton which stores a reference to the "current" IoC container.
1: public class TypeFactory
2: {
3: private static TypeFactory current = new TypeFactory();
4:
5: static TypeFactory()
6: {
7: }
8:
9: protected TypeFactory()
10: {
11: }
12:
13: public static TypeFactory Current
14: {
15: get
16: {
17: return current;
18: }
19:
20: set
21: {
22: current = value;
23: }
24: }
25:
26: public virtual IDependencyInjectionContainer Container
27: {
28: get;
29: set;
30: }
31: }
How to glue these parts together? The InitializeDependencyInjectionContainer() method takes care of initializing the specific IoC container and registers it as current container. This is the only place in the application that is actually "aware" of what IoC container is actually used. I like to have this method run on the Global.ascx for web applications or in the main class for client applications.
1: private static void InitializeDependencyInjectionContainer()
2: {
3: // creating Unity container and registering a sample type
4: IUnityContainer container = new UnityContainer().
5: RegisterType<ISessionRepository, HttpSessionRepository>();
6:
7: // updating the IoC current container to use the Unity Container
8: TypeFactory.Current.Container = new UnityDependencyInjectionContainer(container);
9: }
Finally, in your classes, repositories and factories where type resolution is needed you use the current container registered in the TypeFactory.
1: ISessionRepository repository = TypeFactory.Current.Container.Resolve<ISessionRepository>();
This is a very easy and effective way to accomplish IoC container abstraction and keep your code independent of any specific implementation.