Inversion of Control (IoC) is a software design principle that describes inverting the flow of control in a system, so execution flow is not controlled by a central piece of code. This means that components should only depend on abstractions of other components and are not be responsible for handling the creation of dependent objects. Instead, object instances are supplied at runtime by an IoC container through Dependency Injection (DI).
IoC enables better software design that facilitates reuse, loose coupling, and easy testing of software components.
At first IoC might seem complicated, but it’s actually a very simple concept. An IoC container is essentially a registry of abstract types and their concrete implementations. You request an abstract type from the container and it gives you back an instance of the concrete type. It’s a bit like an object factory, but the real benefits come when you use an IoC container in conjunction with dependency injection.
An IoC container is useful on all types of projects, both large and small. It’s true that large, complex applications benefit most from reduced coupling, but I think it’s still a good practice to adopt, even on a small project. Most small applications don’t stay small for long. As Jimmy Bogard recently stated on Twitter: “the threshold to where an IoC tool starts to show its value is usually around the 2nd hour in the life of a project”.
There are many existing containers to choose from. These have subtle differences, but all aim to achieve the same goal, so it’s really a matter of personal taste which one you choose. Some common containers in .NET are:
While I recommended using one of the containers already available, I am going to demonstrate how easy it is to implement your own basic container. This is primarily to show how simple the IoC container concept is. However, there might be times when you can’t use one of the existing containers, or don’t want all the features of a fully-fledged container. You can then create your own fit-for-purpose container.
Using Dependency Injection with IoC
Dependency Injection is a technique for passing dependencies into an object’s constructor. If the object has been loaded from the container, then its dependencies will be automatically supplied by the container. This allows you to consume a dependency without having to manually create an instance. This reduces coupling and gives you greater control over the lifetime of object instances.
Dependency injection makes it easy to test your objects by allowing you to pass in mocked instances of dependencies. This allows you to focus on testing the behaviour of the object itself, without depending on the implementation of external components or services.
It is good practice to reduce the number of direct calls to the container by only resolving top-level objects. The rest of object-graph will be resolved through dependency injection. This also prevents IoC-specific code from becoming scattered throughout the code base, making it easy switch to a different container if required.
Implementing a Simple IoC Container
To demonstrate the basic concepts behind IoC containers, I have created a simple implementation of an IoC container.
This implementation is loosely based on RapidIoc, created by Sean McAlindin. It does not have all the features of a full IoC container, however, it should be enough to demonstrate the main benefits of using a container.
The SimpleIocContainer class has two public operations: Register and Resolve.
Register is used to register a type with a corresponding concrete implementation. When type is registered, it is added to a list of registered objects.
Resolve is used to get an instance of a type from the container. Depending on the Lifecycle mode, a new instance is created each time the type is resolved (Transient), or only on the first request with the same instance passed back on subsequent requests (Singleton).
Before a type is instantiated, the container resolves the constructor parameters to ensure the object receives its dependencies. This is a recursive operation that ensures the entire object graph is instantiated.
If a type being resolved has not been registered, the container will throw a TypeNotRegisteredException.
Using the Simple IoC Container with ASP.NET MVC
Now I am going to demonstrate using the container by creating a basic order processing application using ASP.NET MVC.
First we create a custom controller factory called SimpleIocControllerFactory that derives from DefaultControllerFactory. Whenever a page is requested, ASP.NET calls GetControllerInstance to get an instance of the page controller. We can then pass back an instance of the controller resolved from our container.
We now need to set SimpleIocControllerFactory as the current controller factory in the global Application_Start handler.
In order for the SimpleIocControllerFactory to resolve an instance of the OrderController, we need to register the OrderController with the container.
Here I have created a static Bootstrapper class for registering types with the container.
The controllers do not contain state, therefore we can use the default singleton lifecycle to create an instance of the controller only once per request.
When we run the application, the OrderController should be resolved and the page will load.
It is worth noting that the controller factory should be the only place we need to explicitly resolve a type from the container. The controllers are top-level objects and all our other objects stem from these. Dependency Injection is used to resolve dependencies down the chain.
To place an order we need to make a call to OrderService from the controller. We inject a dependency to the order service by passing the IOrderService interface into the OrderController constructor.
When we build and run the application we should get an error: “The type IOrderService has not been registered”. This means the container has tried to resolve the dependency, but the type has not been registered with the container. So we need to register IOrderService and its concrete implementation, OrderService, with the container.
The OrderService in turn has a dependency on IOrderRepository which is responsible for inserting the order into a database.
As the OrderService was resolved from the container, we simply need to register an implementation for IOrderRepository for OrderService to receive its dependency.
Any further types are that required simply need to be registered with the container then passed as an argument on the constructor.
Most full-featured IoC containers support some form of auto-registration. This saves you from having do to a lot of one-to-one manual component mappings.
I hope I have demonstrated that IoC containers are not magic. They are in fact a simple concept that, when used correctly, can help to create flexible, loosely-coupled applications.