Introduction
This is the first in a series of articles about using the Munq DI IOC Container and IOC containers in general.
Inversion of Control (IOC) is a pattern which decouples the use of an interface from the concrete implementation of that interface. By eliminating this coupling:
- the code can be tested by using special test implementations of the interface.
- fewer DLLs need to be deployed on each tier of a multi-tiered deployment.
- project compilation may be sped up as there are fewer project dependencies.
- alternate implementations of the interface can be configured for use without requiring changes to the main application code. This could mean changing from MSSQL to Oracle with a simple configuration file change.
An IOC Container is a pattern which allows the application to an instance of the interface without knowing the specific concrete class that will be used.
Munq DI IOC Container is the smallest, fastest IOC container that I know of. It is faster than any of the big players, Unity, Windsor Castle, Ninject, StructureMap, etc. See the graph below. Additionally, it has been designed for the web developer, with lifetime management that includes Request, Session, and Cached lifetime managers.
This article will provide you with a detailed understanding of the Munq IOC Container with:
- a performance graph to grab your attention
- API documentation
Additional articles will demonstrate the usage of the Munq DI IOC Container with real world examples including using Munq with ASP.NET MVC, a tour of the Munq code, and things to watch out for when using IOC Containers.
Background
Earlier this year, I watched the very interesting Funq Screencast Series by Daniel Cazzulino (Kzu). This nine part series detail the TDD development of a small, fast IOC Container called Funq which was destined to evolve into the ContainerModel for the Patterns & Practices: Mobile Application Blocks.
After downloading the code, I discovered a number of improvements which both simplified the code and significantly improved the performance. After several discussions, Kzu made me a contributor and several of my ideas and code were incorporated into the codebase.
Unfortunately, Kzu’s target for Funq was the mobile development space, while I am more involved in ASP.NET Webform and MVC application development. I didn’t need some of the features of Funq such as Container Hierarchies and Initializers, and I did need lifetime management to include web oriented styles including Request, Session, and Cached lifetime management. Thus Munq was conceived.
The goals for this development were:
- Speed. High volume websites need to minimize the amount of work each page request needs to execute. Even 1/10th of a second can make a difference of a user’s perception of the site.
- Simplicity. Munq does one thing, but does it well. It resolves requests for instances of Types by executing Factories that have been previously registered in the container.
- Provide Web Lifetime Management. Windows applications can get by with a container that has the option of creating a new instance each request, or re-use the same instance. In the web application world, objects can have lifetimes that only span the current Request, the user’s Session, or be Cached reduce database load and access delays.
The latest release of Munq is available on CodePlex at http://munq.codeplex.com.
Performance
While developing Funq, Kzu and I created a small performance measuring application to compare the relative overhead of creating new instances form various IOC containers.
This test used the IOC Containers to build up a “WebApp
” which implemented the IWebService
interface which has the dependencies shown in the following diagram:
All Containers were configured to return new instances for all interfaces except ILogger
which was a shared instance. Running 10000 iterations for each use case had the following results.
Container |
Ticks/Iteration |
None |
5.1746 |
Munq |
68.3385 |
Funq |
76.2779 |
Unity |
613.0442 |
Autofac |
877.035 |
StructureMap |
280.9433 |
Ninject |
4122.1138 |
Ninject2 |
5059.9001 |
Windsor |
4206.1035 |
This is shown in the graph below, smaller is better.
The code to register the Factories is as follows:
ILifetimeManager lifetime = new ContainerLifetime();
Container container = new Container();
container.Register<IWebservice>(
c => new WebService(
c.Resolve<IAuthenticator>(),
c.Resolve<IStockquote>()));
container.Register<IAuthenticator>(
c => new Authenticator(
c.Resolve<ILogger>(),
c.Resolve<IErrorhandler>(),
c.Resolve<IDatabase>()));
container.Register<IStockquote>(
c => new StockQuote(
c.Resolve<ILogger>(),
c.Resolve<IErrorhandler>(),
c.Resolve<IDatabase>()));
container.Register<IDatabase>(
c => new Database(
c.Resolve<ILogger>(),
c.Resolve<IErorhandler>()));
container.Register<IErrorhandler>(
c => new ErrorHandler(c.Resolve<ILogger>()));
container.RegisterInstance<ILlogger>(new Logger())
.WithLifetimeManager(lifetime);
Resolving the instance:
var webApp = container.Resolve<IWebservice>();
webApp.Execute();
Using the Code
Using the container is relatively simple. The basic steps are:
- Create the Container.
- Register the factory delegates for the Interfaces and/or Classes.
- Resolve instances by calling
Resolve
methods of the container.
Creating the IOC Container
The container is usually created when the application first starts. In a web application, the container would typically be created in the Application_Start
and stored in a field of the derived Application
class or a static
variable.
Container container = new Container();
Registering Type Factories
Registering the Type Factories associates a type to be resolved with a function that will return an instance of that type. This method is called when the container is asked to return an instance of the type.
Munq has four ways of registering type factory functions. These functions can be anything which has the correct signature and are not limited to delegates of the form
c=> new MyType()
but could be
c => CreateAndInitialzeMyType(c.Resolve<IOne>(), c.Resolve<ITwo>)
The first is using the type-safe generic Register
methods. There are versions for both named and un-named registrations. Both take as a parameter, a delegate which takes a Container
as its single parameter and returns an instance of the type.
public IRegistration Register<TType>(Func<Container, TType> func)
public IRegistration Register<TType>(string name, Func<Container, TType>func)
The second is a set of methods which allows the factory method to be registered by passing the type of be resolved, and a delegate which takes a Container
as its single parameter and returns an Object
. There are versions for named and un-named registrations. These methods would typically be used if the registration information was read from an external store such as a database, XML file, or the web.config file.
public IRegistration Register(Type type, Func<Container, object> func)
public IRegistration Register(string name, Type type, Func<Container, object> func)
The third method is using the type-safe generic RegisterInstance
methods. There are versions for both named and un-named registrations. Both take as a parameter, an instance of the type.
public IRegistration RegisterInstance<TType>(TType instance)
public IRegistration RegisterInstance<TType>(string name, TType instance)
The fourth is a set of methods which allows the factory method to be registered by passing the type of be resolved, and an object to be returned when the type is resolved. There are versions for named and un-named registrations. These methods would typically be used if the registration information was read from an external store such as a database, XML file, or the web.config file.
public IRegistration RegisterInstance(Type type, object instance)
public IRegistration RegisterInstance(string name, Type type, object instance)
Getting an Instance from the Container
Once an interface and function have been registered in the container, the application can retrieve an instance by asking the container to resolve the interface to the concrete implementation that was registered. Munq has two forms of the Resolve
method, a type-safe version using Generics, and a version that takes a Type
as a parameter and returns an Object
. Both versions have named and un-named overloads.
public TType Resolve<TType>()
public TType Resolve<TType>(string name)
public object Resolve(Type type)
public object Resolve(string name, Type type)
Lazy Resolution
There are situations where you may not wish to create the instance immediately. This may be because the instance uses a scarce resource or due to logic that may not require instantiation. For these cases, you can use the LazyResolve
methods which returns a function that, when executed, performs the resolution and returns the instance.
Like the Resolve
methods, the LazyResolve
has two forms, a type-safe version using Generics, and a version that takes a Type
as a parameter. Both versions have named and un-named overloads.
Func LazyResolve<ttype>()
Func<ttype> LazyResolve<ttype<(string name)
Func<Object> LazyResolve(Type type)
Func<Object> LazyResolve(string name, Type type)
What is this IRegistration Thing?
You may have noticed that the Registration
methods all return an object that implements the IRegistration
interface. This interface allows the user to retrieve the internally generated ID for the registration and to specify the LifetimeManager
to be used by the instance when it is resolved.
For example, if you need an IShoppingCart
object to be stored in the users session, you would tell the registration to use the SessionLifetime
manager:
ILifetimeManager lifetime = new SessionLifetime();
container.Register<IShoppingCart>(c => new MyShoppingCart())
.WithLifetimeManager(lifetime);
Notice that this has been implemented in a fluent interface manner to allow changing of method calls. While there is currently only one method on this interface, any future methods will follow this pattern.
public interface IRegistration
{
string Id { get; }
IRegistration WithLifetimeManager(ILifetimeManager manager);
}
Specifying the Default Lifetime Manager
When the Container
is first created, the default behaviour is to return a new instance for each Resolve
method call. As shown above, this can be modified on a registration by registration basis. Additionally, you can specify which lifetime manager is to be used for any registration. For example, to always return the same instance for each call to Resolve
, use the ContainerLifetime
manager as shown below:
Container container = new Container();
ILifetimeManager lifetime = new ContainerLifetime();
container.UsesDefaultLifetimeManagerOf(lifetime);
The method definition is:
public Container UsesDefaultLifetimeManagerOf(ILifetimeManager lifetimeManager)
Available Lifetime Managers
Lifetime Managers allow you to modify the behaviour of the container as to how it resolves instances, and what is the lifetime of the instance. Munq has a set of Lifetime Managers designed for web applications. These are described below.
Warning: if you used the RegisterInstance
method, then the same instance will be returned regardless of which lifetime manager is used.
AlwaysNewLifetime
This lifetime manager’s behaviour is to always return a new instance when the Resolve
method is called by executing the factory method. This is the default behaviour.
ContainerLifetime
This lifetime manager’s behaviour is to always return the same instance when the Resolve
method is called by executing the factory method.
SessionLifetime
This lifetime manager’s behaviour is to always return an attempt to retrieve the instance from Session
when the Resolve
method is called. If the instance does not exist in Session
, a new instance is created by executing the factory method, and storing it in the Session
.
RequestLifetime
This lifetime manager’s behaviour is to always return an attempt to retrieve the instance from Request.Items
when the Resolve
method is called. If the instance does not exist in Request.Items
, a new instance is created by executing the factory method, and storing it in the Request.Items
.
CachedLifetime
This lifetime manager’s behaviour is to always return an attempt to retrieve the instance from Cache
when the Resolve
method is called. If the instance does not exist in Cache
, a new instance is created by executing the factory method, and storing it in the Cache
.
Conclusion
I have given you a brief overview of the Munq DI IOC and its API. I will be following this with additional articles with examples of using Munq and dissecting the Munq code.
The full source for Munq is available on CodePlex at http://munq.codeplex.com.
CodeProject