Unity 中的单例每个调用上下文(Web 请求)

Posted

技术标签:

【中文标题】Unity 中的单例每个调用上下文(Web 请求)【英文标题】:Singleton Per Call Context (Web Request) in Unity 【发布时间】:2010-11-12 04:18:53 【问题描述】:

几天前,我遇到了 ASP.Net 线程问题。我希望每个 Web 请求都有一个单例对象。我的工作单元实际上需要这个。我想为每个 Web 请求实例化一个工作单元,以便身份映射在整个请求中都是有效的。这样,我可以使用 IoC 将我自己的 IUnitOfWork 透明地注入到我的存储库类中,并且我可以使用相同的实例来查询然后更新我的实体。

由于我用的是Unity,所以误用了PerThreadLifeTimeManager。我很快意识到 ASP.Net 线程模型不支持我想要实现的目标。基本上它使用一个线程池并回收线程,这意味着我每个线程得到一个 UnitOfWork !但是,我想要的是每个 Web 请求一个工作单元。

有点谷歌搜索给了我this great post。这正是我想要的。除了很容易实现的统一部分。

这是我对 PerCallContextLifeTimeManager 的统一实现:

public class PerCallContextLifeTimeManager : LifetimeManager

    private const string Key = "SingletonPerCallContext";

    public override object GetValue()
    
        return CallContext.GetData(Key);
    

    public override void SetValue(object newValue)
    
        CallContext.SetData(Key, newValue);
    

    public override void RemoveValue()
    
    

当然,我用它来注册我的工作单元,代码类似于:

unityContainer
            .RegisterType<IUnitOfWork, MyDataContext>(
            new PerCallContextLifeTimeManager(),
            new InjectionConstructor());

希望它可以节省一些时间。

【问题讨论】:

不错的解决方案。如果可以的话,我建议将其重命名为“CallContextLifetimeManager”,因为 Web 请求可能只是潜在应用程序之一。 没错,我更新了文本和代码以反映这一点。谢谢。 使用 PerResolveLifetimeManager 有什么问题? 这不是问题!? 仅供参考,这不是问题的“正确”答案/解决方案。在 ASP.NET 中,单个请求可以(并且通常会在负载很重的情况下)在线程之间跳转。执行此操作时,不会保留 CallContext,只会迁移 HttpContext。如果您希望它在 ASP.NET 中可靠地工作(在负载下),您需要将 CallContext 更改为 HttpContext.Current.Items。 【参考方案1】:

很好的解决方案,但是 LifetimeManager 的每个实例都应该使用唯一的键而不是常量:

private string _key = string.Format("PerCallContextLifeTimeManager_0", Guid.NewGuid());

否则,如果您在 PerCallContextLifeTimeManager 中注册了多个对象,它们将共享相同的密钥来访问 CallContext,您将无法取回预期的对象。

还值得实现 RemoveValue 以确保清理对象:

public override void RemoveValue()

     CallContext.FreeNamedDataSlot(_key);

【讨论】:

-1 CallContext 在每个请求上重新创建。每个请求单例的不同实例之间的键不必是唯一的。 +1 事实上,它必须像六眼说的那样。如果您不分配唯一键,那么所有对象都在单个键下注册,事情就会变得一团糟。 -1 查看 Steven Robbins 的回答和 Micah Zoltu 对问题的评论:在负载下CallContext 不会为单个请求保留。【参考方案2】:

虽然调用这个 PerCallContextLifeTimeManager 很好,但我很确定这 “安全”被视为 ASP.Net Per-request LifeTimeManager。

如果 ASP.Net 进行线程交换,那么通过 CallContext 传递到新线程的唯一就是当前的 HttpContext - 您存储在 CallContext 中的任何其他内容都将消失。这意味着在重负载下,上面的代码可能会产生意想不到的结果——我想追查为什么会很痛苦!

执行此操作的唯一“安全”方法是使用 HttpContext.Current.Items,或执行以下操作:

public class PerCallContextOrRequestLifeTimeManager : LifetimeManager

    private string _key = string.Format("PerCallContextOrRequestLifeTimeManager_0", Guid.NewGuid());

    public override object GetValue()
    
      if(HttpContext.Current != null)
        return GetFromHttpContext();
      else
        return GetFromCallContext();
    

    public override void SetValue(object newValue)
    
      if(HttpContext.Current != null)
        return SetInHttpContext();
      else
        return SetInCallContext();
    

    public override void RemoveValue()
    
    

这显然意味着依赖 System.Web :-(

更多信息请访问:

http://piers7.blogspot.com/2005/11/threadstatic-callcontext-and_02.html

【讨论】:

【参考方案3】:

感谢您的贡献,

但是提出的问题有两个缺点,其中之一是一个严重的错误,正如 Steven Robbins 在his answer 和 Micah Zoltu in a comment 中所述。

    Asp.Net 不保证为单个请求保留调用上下文。在负载下,它可能会切换到另一个,从而导致建议的实现中断。 它不处理在请求结束时释放依赖项。

目前,Unity.Mvc Nuget 包提供了一个 PerRequestLifetimeManager 来完成这项工作。不要忘记在引导代码中注册其关联的UnityPerRequestHttpModule,否则也不会处理依赖释放。

使用引导

DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));

或在 web.config 中 system.webServer/modules

<add name="UnityPerRequestHttpModule" type="Microsoft.Practices.Unity.Mvc.UnityPerRequestHttpModule, Microsoft.Practices.Unity.Mvc" preCondition="managedHandler" />

它的当前实现似乎也适用于 Web 表单。它甚至不依赖于 MVC。不幸的是,它的程序集确实存在,因为它包含一些其他类。

请注意,如果您使用已解析的依赖项使用某些自定义 http 模块,它们可能已经在模块 EndRequest 中处理。这取决于模块执行顺序。

【讨论】:

以上是关于Unity 中的单例每个调用上下文(Web 请求)的主要内容,如果未能解决你的问题,请参考以下文章

在Spring上下文中的原型bean中的单例bean

使用 Unity 的单例模式

基于unity的单例设计模式写法

Windows 激活服务中的单例模式

nodejs中的单例模式 - 是否需要?

spring的单例模式