Unity中的Singleton Per Call上下文(Web请求)

时间:2009-07-19 23:42:41

标签: asp.net singleton unity-container unit-of-work lifetime

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

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

一些谷歌搜索给了我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());

希望能节省一些时间。

3 个答案:

答案 0 :(得分:25)

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

private string _key = string.Format("PerCallContextLifeTimeManager_{0}", Guid.NewGuid());

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

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

public override void RemoveValue()
{
     CallContext.FreeNamedDataSlot(_key);
}

答案 1 :(得分:21)

虽然调用这个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

答案 2 :(得分:4)

感谢您的贡献,

但提出实施的问题有两个缺点,其中一个是Steven Robbins在his answer和Micah Zoltu in a comment已经说过的严重错误。

  1. Asp.Net不保证为单个请求保留呼叫上下文。在负载下,它可以切换到另一个,导致建议的实现中断。
  2. 它不会在请求结束时处理依赖项的释放。
  3. 目前,Unity.Mvc Nuget软件包提供了PerRequestLifetimeManager来完成工作。不要忘记在引导代码中注册其关联的UnityPerRequestHttpModule,否则也不会处理释放依赖关系。

    使用bootstrapping

    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中。这取决于模块执行顺序。