65.9K
CodeProject 正在变化。 阅读更多。
Home

Munq IOC 容器 – 创建线程本地存储生命周期管理器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2010年5月24日

CPOL

4分钟阅读

viewsIcon

20265

Munq IOC 容器 – 创建线程本地存储生命周期管理器

目录

“您有邮件”

在我开始撰写关于在 ASP.NET MVC2 应用程序中使用 Munq IocContainer V2 的第二篇文章时,我收到了以下电子邮件。

Miro Bartanus 在“[文章]:Munq IOC 容器在 ASP.NET 中的介绍”上发表了新评论:

嗨 Matthew,Munq 非常好,我只是想知道它是否支持 TLS 作为生命周期,我今天晚些时候会查看源代码,但你可能知道,或者实现起来应该不难……

我心想,“这真是个好主意。” 我已经计划了一篇关于创建生命周期管理器的文章,但还不确定类型。ThreadLocalStorageLifetimeManager(线程本地存储生命周期管理器)相对容易实现,而且它是我现在需要解决的一些编程问题,因为我已经开始使用 .NET 4 中的并行编程库了。

什么是生命周期管理器?

LifetimeManager(生命周期管理器)控制 IOC 容器在被要求解析类型时实例的重用。通过调用 UsesDefaultLifetimeManagerOf 方法,可以在 Container 上将解析时使用的 LifetimeManager 指定为默认值。以下示例将默认 LifetimeManager 设置为 RequestLifetimeManger,从而使实例在每个 HTTP 请求的持续时间内重用。

// 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);

或者,您可以在 RegisterXXX 调用返回的 IRegistration 实例上调用 WithLifetimeManager 方法。以下示例注册了两个服务,并导致容器始终返回相同的实例,从而有效地使其成为单例。

// 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);

有哪些可用的生命周期管理器?

Munq 在 2.0 版本中包含许多 LifetimeManager(生命周期管理器)。下面将对它们进行描述。我将在未来的小版本中添加 ThreadLocalStorageLifetimeManger

警告:如果您使用了 RegisterInstance 方法,那么无论使用哪种生命周期管理器,都会返回相同的实例。

AlwaysNewLifetime
此生命周期管理器的行为是,通过执行工厂方法,在调用 Resolve 方法时始终返回一个新实例。这是默认行为。
ContainerLifetime
此生命周期管理器的行为是,通过执行工厂方法,在调用 Resolve 方法时始终返回同一个实例。该实例被缓存到容器本身。
SessionLifetime
此生命周期管理器的行为是,在调用 Resolve 方法时,始终尝试从 Session 中检索实例。如果实例不存在于 Session 中,则通过执行工厂方法创建新实例,并将其存储在 Session 中。
RequestLifetime
此生命周期管理器的行为是,在调用 Resolve 方法时,始终尝试从 Request.Items 中检索实例。如果实例不存在于 Request.Items 中,则通过执行工厂方法创建新实例,并将其存储在 Request.Items 中。
CachedLifetime
此生命周期管理器的行为是,在调用 Resolve 方法时,始终尝试从 Cache 中检索实例。如果实例不存在于 Cache 中,则通过执行工厂方法创建新实例,并将其存储在 Cache 中。CachedLifetimeManager 可以应用 CacheDependencies 和滑动或绝对超时。

检查现有的生命周期管理器

生命周期管理器实现 ILifetimeManager 接口及其两个方法。第一个方法 GetInstance 从生命周期管理器的缓存中获取请求的实例,或者在没有缓存实例时创建一个新实例。第二个方法 InvalidateInstanceCache 移除任何先前创建和缓存的实例,强制在下一个解析请求时创建新实例。

public interface ILifetimeManager
{
    object GetInstance(IInstanceCreator creator);
    void InvalidateInstanceCache(IRegistration registration);
}

下面是 RequestLifetimeManager 的代码。在 GetInstance 方法中,代码使用传入的 IInstanceCreator(创建者)的 Key 属性尝试从 HttpContext.Request.Items 集合中检索实例。如果实例不存在,则调用 IInstanceCreator CreateInstance 方法,指定不将实例缓存到容器中。这会返回所需类型的新实例,并将其存储在 HttpContext.Request.Items 集合中以备将来重用。

InvalidateInstanceCache 方法只是移除任何存储的实例,强制在下一个解析请求时创建新实例。

其他代码用于支持测试,可以忽略。

using System.Web;

namespace Munq.LifetimeManagers
{
    public class RequestLifetime : ILifetimeManager
    {
        private HttpContextBase testContext;

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

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

创建新的生命周期管理器

现在您已经具备了创建新 LifetimeManager 所需的知识。基本步骤是:

  1. 创建一个派生自 ILifetimeManger 的类
  2. 实现这两个方法

我将在 Munq.IocContainer 的源代码中创建这些,因为我认为这是一个很好的补充,但您也可以创建一个新项目并引用 Munq.Interfaces。然后,如果您需要自定义 LifetimeManager,您将引用您的 DLL。

该类需要一个线程本地字典或哈希表来存储实例。否则,代码与 RequestLifetimeManager 的代码基本相同。完整代码如下:

>using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Munq.LifetimeManagers
{
    /// <summary>
    /// A LifetimeManager that uses Thread Local Storage to cache instances.
    /// </summary>
    public class ThreadLocalStorageLifetime : ILifetimeManager
    {
        // The thread local storage.  The ThreadStatic attribute makes this easy.
        [ThreadStatic]
        static Dictionary<string, object> localStorage;

        /// <summary>
        /// Gets an instance from the thread local storage, 
        /// or creates a new instance if not found.
        /// </summary>
        /// <param name="creator">The IInstanceCreate to use to get the Key 
        /// and create new if required.</param>
        /// <returns>The instance.</returns>
        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;
        }

        /// <summary>
        /// Removes the instance for the registration from the local storage cache.
        /// </summary>
        /// <param name="registration">The IRegistration returned 
        /// when the type was registered in the IOC container.</param>
        public void InvalidateInstanceCache(IRegistration registration)
        {
            // nothing stored yet
            if (localStorage == null)
                return;

            localStorage.Remove(registration.Key);
        }
    }
}

此代码的单元测试是:

>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
{      
    /// <summary>
    ///This is a test class for ThreadLocalStorageLifetimeTest and is intended
    ///to contain all ThreadLocalLifetimeTest Unit Tests
    ///</summary>
    [TestClass()]
    public class ThreadLocalStorageLifetimeTest
    {
        private TestContext testContextInstance;

        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        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

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

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

        /// <summary>
        /// verify Request Lifetime returns same instance for same request, 
        /// different for different request
        /// </summary>
        [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);
        }
    }
}

结论

添加自己的自定义生命周期管理器非常简单,它允许您支持您已经编写或使用的任何自定义数据存储、缓存、会话等。如何实现一个 AppFabricLifetimeManager 呢?

请关注后续文章。

© . All rights reserved.