Table of Contents
Just as I was starting on my second article about Using Munq IocContainer V2 in ASP.NET MVC2 Applications, I received the following email.
Miro Bartanus has posted a new comment at "[Article]: Introduction to Munq IOC Container for ASP.NET":
Hi Matthew, Munq is very nice, I am just wondering if it supports TLS as a Lifetime, I will check sources later today, but you maid (might) know, or that should not be too difficult to implement...
and I thought, “What a great idea.” I’d already planned an article on creating a Lifetime Manager, but wasn’t sure of the type. A ThreadLocalStorageLifetimeManager
is relatively easy, and it's something I need to solve some of my own programming issues now that I’ve started to use the Parallel Programming library in .NET 4.
A LifetimeManager
controls the reuse of instances when the IOC Container is asked to Resolve a type. The LifetimeManager
used when resolving can be specified as a default on the Container
by calling the UsesDefaultLifetimeManagerOf
method. The following example sets the default LifetimeManager
to the RequestLifetimeManger,
causing instances to be reused for the duration of each HTTP Request.
IIocContainer iocContainer = new Container();
ILifetimeManager lifetimeManager = new LifetimeManagers.RequestLifetime();
iocContainer.UsesDefaultLifetimeManagerOf(lifetimeManager);
Alternately, you can call the WithLifetimeManager
method on the IRegistration
instance returned from the RegisterXXX
call. The following example registers two services and causes the same instance to always be returned from the container, effectively making them singletons.
IIocContainer iocContainer = new Container();
ILifetimeManager containerLifetimeManager = new LifetimeManagers.ContainerLifetime();
iocContainer.Register<IMembershipService>( ioc =>
new AccountMembershipService(Membership.Provider))
.WithLifetimeManager(containerLifetimeManager);
iocContainer.Register<IFormsAuthenticationService>
(ioc => new FormsAuthenticationService())
.WithLifetimeManager(containerLifetimeManager);
Munq has a number of LifetimeManager
s included with the version 2.0 release. These are described below. I will be adding the ThreadLocalStorageLifetimeManger
to a future point release.
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. The instance is cached in the container itself.
- 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
, then 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
, then 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
, then 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 CachedLifetimeManager
.
A Lifetime Manager implements the ILifetimeManager
interface and its two methods. The first method, GetInstance
, gets the requested instance from the LifetimeManager
’s cache, or creates an new instance if there is no cached instance. The second method, InvalidateInstanceCache
, removes any previously created and cached instance, forcing a new instance to be created on the next resolve request.
public interface ILifetimeManager
{
object GetInstance(IInstanceCreator creator);
void InvalidateInstanceCache(IRegistration registration);
}
Below is the code for the RequestLifetimeManager
. In the GetInstance
method, the code attempts to retrieve an instance from the HttpContext.Request.Items
collection using the Key
property of the IInstanceCreator
, creator, passed in. If the instance does not exist, the IInstanceCreator CreateInstance
method is called, specifying that the instance is not to be cached in the container. This returns a new instance of the required type, and it is stored in the HttpContext.Request.Items
collection for future reuse.
The InvalidateInstanceCache
method just removes any stored instance, forcing the creation of a new instance on the next resolve request.
The other code is to support testing and can be ignored.
using System.Web;
namespace Munq.LifetimeManagers
{
public class RequestLifetime : ILifetimeManager
{
private HttpContextBase testContext;
private HttpContextBase Context
{
get
{
HttpContextBase context = (HttpContext.Current != null)
? new HttpContextWrapper(HttpContext.Current)
: testContext;
return context;
}
}
#region ILifetimeManage Members
public object GetInstance(IInstanceCreator creator)
{
object instance = Context.Items[creator.Key];
if (instance == null)
{
instance = creator.CreateInstance
(ContainerCaching.InstanceNotCachedInContainer);
Context.Items[creator.Key] = instance;
}
return instance;
}
public void InvalidateInstanceCache(IRegistration registration)
{
Context.Items.Remove(registration.Key);
}
#endregion
public void SetContext(HttpContextBase context)
{
testContext = context;
}
}
}
Now you have enough knowledge to create a new LifetimeManager
. The basic steps are:
- Create a class derived from
ILifetimeManger
- Implement the two methods
I am going to create these in the source for Munq.IocContainer
because I think this a great addition, but you could create a new project and include a reference to Munq.Interfaces
. Then if you needed your custom LifetimeManager
, you would reference your DLL.
The class needs a thread local dictionary or hashtable to store the instances. Otherwise, the code is just about the same as the code for RequestLifetimeManager
. The full code is:
>using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Munq.LifetimeManagers
{
public class ThreadLocalStorageLifetime : ILifetimeManager
{
[ThreadStatic]
static Dictionary<string, object> localStorage;
public object GetInstance(IInstanceCreator creator)
{
object instance = null;
if (localStorage == null)
localStorage = new Dictionary<string,object>();
if (!localStorage.TryGetValue(creator.Key, out instance))
{
instance = creator.CreateInstance
(ContainerCaching.InstanceNotCachedInContainer);
localStorage[creator.Key] = instance;
}
return instance;
}
public void InvalidateInstanceCache(IRegistration registration)
{
if (localStorage == null)
return;
localStorage.Remove(registration.Key);
}
}
}
Unit tests for this are:
>using Munq.LifetimeManagers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using Munq;
using System.Web;
using MvcFakes;
using Munq.FluentTest;
using System.Threading.Tasks;
namespace Munq.Test
{
[TestClass()]
public class ThreadLocalStorageLifetimeTest
{
private TestContext testContextInstance;
public TestContext TestContext
{
get
{
return testContextInstance;
}
set
{
testContextInstance = value;
}
}
#region Additional test attributes
IIocContainer iocContainer;
[TestInitialize()]
public void MyTestInitialize()
{
iocContainer = new Munq.Container();
}
[TestCleanup()]
public void MyTestCleanup()
{
var regs = iocContainer.GetRegistrations<IFoo>();
regs.ForEach(reg => iocContainer.Remove(reg));
iocContainer.Dispose();
}
#endregion
[TestMethod()]
public void CanSetDefaultLifetimeManagerToThreadLocalStorageLifetime()
{
var lifetime = new ThreadLocalStorageLifetime();
iocContainer.UsesDefaultLifetimeManagerOf(lifetime);
Verify.That(iocContainer.LifeTimeManager).IsTheSameObjectAs(lifetime);
}
[TestMethod]
public void ThreadLocalStorageLifetimeManagerReturnsSameObjectForSameRequest()
{
var requestltm = new ThreadLocalStorageLifetime();
var container = new Container();
container.Register<IFoo>(c => new Foo1())
.WithLifetimeManager(requestltm);
IFoo result1 = container.Resolve<IFoo>();
IFoo result2 = container.Resolve<IFoo>();
IFoo result3=null;
IFoo result4=null;
var t = Task.Factory.StartNew(() =>
{
result3 = container.Resolve<IFoo>();
result4 = container.Resolve<IFoo>();
});
t.Wait();
Verify.That(result3).IsNotNull();
Verify.That(result4).IsNotNull()
.IsTheSameObjectAs(result3);
Verify.That(result2).IsNotNull();
Verify.That(result1).IsNotNull()
.IsTheSameObjectAs(result2)
.IsNotTheSameObjectAs(result3);
}
}
}
Adding your own custom Lifetime Manager is simple, and allows you to support any custom data storage, caching, sessions, etc. that you may have written or use. How about an AppFabricLifetimeManager
?
Please watch for future articles.