Table of Contents
- Downloading the code
- Overview
- What is Munq IocContainer
- Using Munq IocContainer
- Registering Factory Methods
- Obtaining an Instance from the IocContainer
- Initializing the IocContainer
- Lifetime Management
Downloading the Code
The code is maintained at CodePlex. The latest release can be found here.
Overview
This post is the first of several heralding the release of the first beta of the second release Munq IocContainer. In previous posts, Introduction to Munq IOC Container for ASP.NET and Using Munq IOC with ASP.NET MVC 2 Preview 2, I introduced an IOC Container that is high performance and designed for ASP.NET. This latest release fixes a number of small issues and adds functionality without sacrificing any of the performance. Unit tests were created to ensure the quality and correctness of the implement. I felt this was critical as several people have enquired about using Munq IocContainer in production code.
In addition, I will be releasing Munq FluentTest, a fluent interface to be used with MSTest or NUnit to allow the verification statements in a test to be written in an almost English syntax. Examples can be seen in the unit tests for the IocContainer.
The first articles in this series will be:
- Munq IocContainer V2 – Overview (this article)
- Munq IocContainer V2 – Using Munq in an ASP.NET application (may be in two parts)
- Munq IocContainer V2 – Using Lifetime Management to Eliminate Unnecessary Instance Creation
- Munq IocContainer V2 – Using Munq to Implement an Automatic Plug-in or Modular Application
- Munq IocContainer V2 – Creating a custom Lifetime Manager.
The previous list will be updated as changes and wild ideas occur.
What is Munq IocContainer
If you have been, or trying to, program using the SOLID principles then you are implementing your classes with Dependency Inversion, the D in SOLID. Simply stated, Dependency Inversion moves the responsibility of resolving dependencies to the caller of a method or constructor, instead the method or constructor. This makes the called method easier to test as the dependencies can be substituted with a mock or stub by unit test code. At its simplest, you can use the Poor Man’s Dependency Injection pattern. This involve using two constructors, one which has the dependencies passed in, and a default (parameter-less constructor) that calls the other constructor with default dependency implementation. This is shown in the code snippet below:
public</span /> class</span /> MyClass
{
</span />
</span /> IDependencyOne _dependencyOne;
IDependencyTwo _depencencyTwo;
</span /> public</span /> MyClass(IDepenencyOne dependOne, IDependencyTwo dependTwo)
{
_dependencyOne = dependOne;
_dependencyOne = dependTwo;
}
</span />
</span /> public</span /> MyClass() : this</span />(new</span /> ImplDependOne(), new</span /> ImplDependTwo())
{
}
public</span /> void</span /> AMethod(int</span /> a)
{
var</span /> b = _dependencyOne.MethodA(a);
_dependencyTwo.MethodB(b);
}
...
}
Production code calls the default constructor, and unit tests use the constructor with parameters. Unfortunately, this still couples MyClass to the concrete implementations ImplDependOne and ImplDependTwo.
This dependency can be removed by removing the default constructor. However, this now means that every place that an instance of MyClass is created, the code must also create instances of classes implementing IDependencyOne and IDependencyTwo. This is where an IOC Container comes in. IOC Containers can be queried for an implementation of an interface or abstract class, and will return an instance with all of its dependencies resolved.
With Munq IocContainer, this requires two steps. The first is the registration of a factory method with the container, and second, using the container to resolve the dependency. Munq IocContainer registers methods that take an instance of IocContainer and returns an instance that implements the requested interface. By using factory methods instead of classes, the developer has complete control over what is done to create the instance. Furthermore, because there is no need to use Reflection or other CPU expensive techniques to select a constructor and resolve its dependencies, the resolution speed of the container is as fast as possible. Thus the previous example would look like:
public</span /> class</span /> MyClass : IMyClass
{
</span />
</span /> IDependencyOne _dependencyOne;
IDependencyTwo _depencencyTwo;
</span /> public</span /> MyClass(IDepenencyOne dependOne, IDependencyTwo dependTwo)
{
_dependencyOne = dependOne;
_dependencyOne = dependTwo;
}
public</span /> void</span /> AMethod(int</span /> a)
{
var</span /> b = _dependencyOne.MethodA(a);
_dependencyTwo.MethodB(b);
}
...
}
...
</span />
</span />public</span /> static</span /> Container { get</span />; private</span /> set</span />;}
private</span /> void</span /> InitIocContainer()
{
</span /> Container = new</span /> IocContainer();
</span /> Container.Register<IMyClass>(c => new</span /> MyClass(c.Resolve<IDependencyOne>(), c.Resolve<IDependencyTwo>()) );
Container.Register<IDependencyOne>(c => new</span /> ImplDependOne() );
Container.Register<IDependencyTwo>(c => new</span /> ImplDependTwo() );
}
...
</span />
</span /> IMyClass instance = MyApp.Container.Resolve<IMyClass>();
instance.MethodA(42</span />);
If you are familiar with any of the more well known IOC Containers such as Unity, NInject, AutoFac, Windsor, or StructureMap, you will be thinking, “This is just the same as all the others”. And you would be mainly correct. The difference is in the speed of the Resolve functionality and Web oriented lifetime management features. For some details on the performance of Munq compared to the other IOC Containers see my previous article Introduction to Munq IOC Container for ASP.NET. You can also get someone else’s opinion at Munq is for web, Unity is for Enterprise by Omar AL Zabir, creator of DropThings. But just to recap, the relative performances of the IOC containers is show below, smaller is better.
Using Munq IocContainer
Version 2 of Munq has been refactored into two DLLs. The first, Munq.Interfaces, contains only the interfaces required to use an instance of the IIocContainer, an IRegistration object (returned from one the IIocContainer.Register methods), or create your own lifetime manager. By programming to the interfaces, only the main application requires a hard dependency on the Munq IOC Container. Other DLLs can be passed the container instance as an IIocContainer. This will allow the replacement of the IOC Container with another implementation if desired.
The second DLL, Munq.IocContainer contains the implementation of the Munq IOC Container, the standard Lifetime Managers, a configuration loader, and a Controller Factory for ASP.NET MVC.
Registering Factory Methods
Munq allows you to Register a named or unnamed factory method using Generic or Non-Generic methods, for a total of 4 different signatures for the Register method. Similarly, there are 4 methods for registering a pre-constructed instance to always be returned on a Resolve request.
</span /> IRegistration Register(string</span /> name, Type type, Func<</span />IIocContainer, object</span />></span /> func);
IRegistration Register(Type type, Func<</span />IIocContainer, object</span />></span /> func);
IRegistration Register<</span />TType></span />(Func<</span />IIocContainer, TType></span /> func) where TType : class</span />;
IRegistration Register<</span />TType></span />(string</span /> name, Func<</span />IIocContainer, TType></span /> func) where TType : class</span />;
</span /> IRegistration RegisterInstance(string</span /> name, Type type, object</span /> instance);
IRegistration RegisterInstance(Type type, object</span /> instance);
IRegistration RegisterInstance<</span />TType></span />(string</span /> name, TType instance) where TType : class</span />;
IRegistration RegisterInstance<</span />TType></span />(TType instance) where TType : class</span />;
All the RegisterXXX methods return an object that implements the IRegistration interface. This interface allows you to get information about the registration, specify a LifetimeManger, of invalidate any cached instances.
public</span /> interface</span /> IRegistration
{
string</span /> Name { get</span />; }
string</span /> Key { get</span />; }
Type ResolvesTo { get</span />; }
IRegistration WithLifetimeManager(ILifetimeManager manager);
void</span /> InvalidateInstanceCache();
}
Obtaining an Instance from the IocContainer
The user asks the container to create, or serve up a cached instance, of an implementation of an interface by calling one of the Resolve methods. For example:
</span />IMyClass instance = MyApp.Container.Resolve<</span />IMyClass></span />();
instance.MethodA(42</span />);
The container returns a instance of the required type, with all its dependencies resolved and fully initialized.
</span />object</span /> Resolve(string</span /> name, Type type);
object</span /> Resolve(Type type);
TType Resolve<</span />TType></span />() where TType : class</span />;
TType Resolve<</span />TType></span />(string</span /> name) where TType : class</span />;
In many cases, you may not wish to actually create the instance due to the cost of construction, or use of scarce resources. Instead, you want to delay the construction until you need it. For these cases, call one of the LazyResolve methods.
</span />Func<</span />object</span />></span /> LazyResolve(string</span /> name, Type type);
Func<</span />object</span />></span /> LazyResolve(Type type);
Func<</span />TType></span /> LazyResolve<</span />TType></span />() where TType : class</span />;
Func<</span />TType></span /> LazyResolve<</span />TType></span />(string</span /> name) where TType : class</span />;
These will return a delegate that will get the instance from the container, when and if you need it.
</span />Func<</span />MyClass></span /> lazyLoader = Container.LazyResolve<</span />MyClass></span />();
</span /> ...
if</span /> (INeedMyClass)
{
using</span />(MyClass myClass = lazyLoader())
{
</span /> ...
}
}
Initializing the IOC Container
Initialization of the IOC container can be performed in a number of ways, but usually occurs in the Application_Start of the Global.asax file for ASP.NET applications. Out of the box, Munq supports:
- initialization by code
- automatic discovery and registration
Typically, applications will use a combination of both. For example, the code below has an InitializeIOC method which calls the ConfigurationLoader.FindAndRegisterDependencies method and also the Register<..> method.
The FindAndRegisterDependencies method is passed the IIocContainer and does:
- Searches the bin directory for any classes that implement the IMunqConfig interface.
- For each of these classes it creates an instance and calls the RegisterIn method, passing in the container.
- These methods allow the module to register its classes and dependencies with the container.
The explicit registration allows the factory method and its dependencies to be registered. Examine the Register<IController>(“Account”, …) statement below. The line translated into English says, “When asked for an instance of the IController interface, named “Account”, create an instance by calling the constructor. Pass in to the constructor instances of IFormsAuthentication and IMembershipService, both of which are also resolved by the container, as are any of their dependencies.”
public</span /> class</span /> MvcApplication : System.Web.HttpApplication
{
public</span /> static</span /> Container IOC { get</span />; private</span /> set</span />; }
public</span /> static</span /> void</span /> RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("</span />{resource}.axd/{*pathInfo}"</span />);
routes.MapRoute(
"</span />Default"</span />,
</span /> "</span />{controller}/{action}/{id}"</span />,
</span /> new</span /> { controller = "</span />Home"</span />, action = "</span />Index"</span />, id = "</span />"</span /> }
</span /> );
}
protected</span /> void</span /> Application_Start()
{
InitializeIOC();
RegisterRoutes(RouteTable.Routes);
}
private</span /> void</span /> InitializeIOC()
{
</span /> IOC = new</span /> Container();
</span /> var</span /> controllerFactory = new</span /> MunqControllerFactory(IOC);
</span /> ControllerBuilder.Current.SetControllerFactory(controllerFactory);
ConfigurationLoader.FindAndRegisterDependencies(IOC);
</span /> IOC.Register<IController>("</span />Home"</span />, ioc => new</span /> HomeController());
IOC.Register<IController>("</span />Account"</span />,
ioc => new</span /> AccountController(ioc.Resolve<IFormsAuthentication>(),
ioc.Resolve<IMembershipService>())
);
}
}
Lifetime Management
Lifetime Management functionality has not changed since version 1, although the implementation has.
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 a the same instance when the Resolve
method is called by executing the factory method. The instance is cached in the container itself.
SessionLifetime
This lifetime manager’s behaviour is to always return a attempt to retrieve the instance from Session
when the Resolve
method is called. If the instance does not exist in Session
, the 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 a attempt to retrieve the instance from Request.Items
when the Resolve
method is called. If the instance does not exist in Request.Items
, the 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 a attempt to retrieve the instance from Cache
when the Resolve
method is called. If the instance does not exist in Cache
, the a new instance is created by executing the factory method, and storing it in the Cache
. CacheDependencies and Sliding or Absolute Timeouts can be applied to the the CachedLifetimeManager.
Conclusion
This article was just a brief overview of the functionality and performance of the Munq IocContainer. I will examine different aspects of using the Munq.IocContainer in other articles in this series, so stay tuned.