Click here to Skip to main content
Click here to Skip to main content

Munq IOC Container – Creating a Thread Local Storage Lifetime Manager

, 24 May 2010
Rate this:
Please Sign up or sign in to vote.
Munq IOC Container – Creating a Thread Local Storage Lifetime Manager

Table of Contents

  • “You have mail”
  • What is a Lifetime Manager?
  • What Lifetime Managers are available?
  • Examining an Existing Lifetime Manager
  • Creating a new Lifetime Manager
  • Conclusion

“You Have Mail”

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.

What is a Lifetime Manager?

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.

// create the container.  Only done once in Application_Start
IIocContainer iocContainer = new Container();

// create a lifetime manager to use as default
ILifetimeManager lifetimeManager = new LifetimeManagers.RequestLifetime();

// set the default lifetime manager
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.

// create the container.  Only done once in Application_Start
IIocContainer iocContainer = new Container();

// create a Container lifetime manager to use for 'singleton' services
// only one instance will be created and reused for each resolve request.
ILifetimeManager containerLifetimeManager = new LifetimeManagers.ContainerLifetime();

iocContainer.Register<IMembershipService>( ioc => 
	new AccountMembershipService(Membership.Provider))
            .WithLifetimeManager(containerLifetimeManager);

iocContainer.Register<IFormsAuthenticationService>
	(ioc => new FormsAuthenticationService())
            .WithLifetimeManager(containerLifetimeManager);

What Lifetime Managers are Available?

Munq has a number of LifetimeManagers 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.

Examining an Existing Lifetime Manager

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;

         /// <span class="code-SummaryComment"><summary>
</span>        /// Return the HttpContext if running in a web application, the test 
        /// context otherwise.
        /// <span class="code-SummaryComment"></summary>
</span>        private HttpContextBase Context
        {
            get
            {
                HttpContextBase context = (HttpContext.Current != null)
                                ? new HttpContextWrapper(HttpContext.Current)
                                : testContext;
                return context;
            }
        }

       #region ILifetimeManage Members
        /// <span class="code-SummaryComment"><summary>
</span>        /// Gets the instance from the Request Items, if available, 
        /// otherwise creates a new
        /// instance and stores in the Request Items.
        /// <span class="code-SummaryComment"></summary>
</span>        /// <span class="code-SummaryComment"><param name="creator">The creator (registration) to create a new instance.
</span>        /// <span class="code-SummaryComment"></param>
</span>        /// <span class="code-SummaryComment"><returns>The instance.</returns>
</span>        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;
        }
        /// <span class="code-SummaryComment"><summary>
</span>        /// Invalidates the cached value.
        /// <span class="code-SummaryComment"></summary>
</span>        /// <span class="code-SummaryComment"><param name="registration">The Registration 
</span>        /// which is having its value invalidated<span class="code-SummaryComment"></param>
</span>        public void InvalidateInstanceCache(IRegistration registration)
        {
            Context.Items.Remove(registration.Key);
        }

        #endregion

        // only used for testing.  Has no effect when in web application
        public void SetContext(HttpContextBase context)
        {
            testContext = context;
        }
    }
}

Creating a new Lifetime Manager

Now you have enough knowledge to create a new LifetimeManager. The basic steps are:

  1. Create a class derived from ILifetimeManger
  2. 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
{
    /// <span class="code-SummaryComment"><summary>
</span>    /// A LifetimeManager that uses Thread Local Storage to cache instances.
    /// <span class="code-SummaryComment"></summary>
</span>    public class ThreadLocalStorageLifetime : ILifetimeManager
    {
        // The thread local storage.  The ThreadStatic attribute makes this easy.
        [ThreadStatic]
        static Dictionary<string, object> localStorage;

        /// <span class="code-SummaryComment"><summary>
</span>        /// Gets an instance from the thread local storage, 
        /// or creates a new instance if not found.
        /// <span class="code-SummaryComment"></summary>
</span>        /// <span class="code-SummaryComment"><param name="creator">The IInstanceCreate to use to get the Key 
</span>        /// and create new if required.<span class="code-SummaryComment"></param>
</span>        /// <span class="code-SummaryComment"><returns>The instance.</returns>
</span>        public object GetInstance(IInstanceCreator creator)
        {
            object instance = null;

            // if it is a new thread then the localStorage needs to be initialized;
            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;
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Removes the instance for the registration from the local storage cache.
        /// <span class="code-SummaryComment"></summary>
</span>        /// <span class="code-SummaryComment"><param name="registration">The IRegistration returned 
</span>        /// when the type was registered in the IOC container.<span class="code-SummaryComment"></param>
</span>        public void InvalidateInstanceCache(IRegistration registration)
        {
            // nothing stored yet
            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
{      
    /// <span class="code-SummaryComment"><summary>
</span>    ///This is a test class for ThreadLocalStorageLifetimeTest and is intended
    ///to contain all ThreadLocalLifetimeTest Unit Tests
    ///<span class="code-SummaryComment"></summary>
</span>    [TestClass()]
    public class ThreadLocalStorageLifetimeTest
    {
        private TestContext testContextInstance;

        /// <span class="code-SummaryComment"><summary>
</span>        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///<span class="code-SummaryComment"></summary>
</span>        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        #region Additional test attributes
        // 
        //You can use the following additional attributes as you write your tests:
        //
        //Use ClassInitialize to run code before running the first test in the class
        //[ClassInitialize()]
        //public static void MyClassInitialize(TestContext testContext)
        //{
        //}

        IIocContainer iocContainer;
        // Use TestInitialize to run code before running each test 
        [TestInitialize()]
        public void MyTestInitialize()
        {
            iocContainer = new Munq.Container();
        }

        // Use TestCleanup to run code after each test has run
        [TestCleanup()]
        public void MyTestCleanup()
        {
            // remove the registrations, and cache values

            var regs = iocContainer.GetRegistrations<IFoo>();
            regs.ForEach(reg => iocContainer.Remove(reg));

            iocContainer.Dispose();
        }
        #endregion

        /// <span class="code-SummaryComment"><summary>
</span>        /// Verify that Can Set the DefaultLifetimeManager To ThreadLocalStorageLifetime
        ///<span class="code-SummaryComment"></summary>
</span>        [TestMethod()]
        public void CanSetDefaultLifetimeManagerToThreadLocalStorageLifetime()
        {
            var lifetime = new ThreadLocalStorageLifetime();
            iocContainer.UsesDefaultLifetimeManagerOf(lifetime);

            Verify.That(iocContainer.LifeTimeManager).IsTheSameObjectAs(lifetime);
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// verify Request Lifetime returns same instance for same request, 
        /// different for different request
        /// <span class="code-SummaryComment"></summary>
</span>        [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;

            // get values on a different thread
            var t = Task.Factory.StartNew(() =>
            {
                result3 = container.Resolve<IFoo>();
                result4 = container.Resolve<IFoo>();
            });

            t.Wait();

            // check the results
            Verify.That(result3).IsNotNull(); 
            Verify.That(result4).IsNotNull()
                        .IsTheSameObjectAs(result3);

            Verify.That(result2).IsNotNull();
            Verify.That(result1).IsNotNull()
                        .IsTheSameObjectAs(result2)
                        .IsNotTheSameObjectAs(result3);
        }
    }
}

Conclusion

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Matthew Dennis
Software Developer (Senior) The Code Project
Canada Canada
Matthew works on improving the performance and experience of the Code Project site for users, clients, and administrators.
 
Matthew has more years of software develeopment, QA and architecture experience under his belt than he likes to admit. He gradutated for the University of Waterloo with a B.Sc. in Electrical Engineering. He started out developing micro-processor based hardware and software including compilers and operating systems.
His current focus is on .NET web development including jQuery, Webforms, MVC, AJAX, and patterns and practices for creating better websites.
He is the author of the Munq IOC, the fastest ASP.NET focused IOC Container.
His non-programming passions include golf, pool, curling, reading and building stuff for the house.
Follow on   Twitter

Comments and Discussions

 
GeneralMy vote of 5 Pinmembericaman15-Oct-10 3:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 24 May 2010
Article Copyright 2010 by Matthew Dennis
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid