Application_BeginRequest()方法中的依赖项注册

Dmi*_*rii 3 .net asp.net dependency-injection ioc-container unity-container

编写以下代码是否正常:

protected void Application_BeginRequest()
{
    var container = new UnityContainer()
        .RegisterType<IUnitOfWork, MyEntities>()

    HttpContext.Current.Items["container"] = container;

    ServiceLocator.SetLocatorProvider(
        () => new UnityServiceLocator(container));
}

protected void Application_EndRequest()
{
    var container = HttpContext.Current.Items["container"] 
        as UnityContainer;

    if (container != null)
    {
        container.Dispose();
    }
}
Run Code Online (Sandbox Code Playgroud)

或者为每个请求注册依赖项是一种不好的做法?

谢谢.

Ste*_*ven 5

注意:以下建议适用于所有IoC框架,并非针对Unity.

TLDR:不要这样做.一旦注册容器Application_StartServiceLocator.SetLocatorProvider只调用一次.

可以将依赖项注册为"每个请求"或"每个Web请求"生命周期.但是,每个Web请求创建一个新的容器实例是一种非常糟糕的做法.

容器被优化用于解析实例(不用于注册),这通常意味着当第一次请求类型(从容器中)时,会有一些动态代码生成.注册和代码生成和编译过程可能非常耗时,并且在每个Web请求上创建容器的新实例将重新触发完整的注册和代码生成过程.这需要时间,生成许多临时对象(要收集更多垃圾),并生成必须至少在该容器实例的生命周期内缓存的新代码.

此外,拥有多个容器实例使得很难进行其他优化,例如注册范围大于每个Web请求的实例.在您的情况下,将类型注册为"singleton"实际上意味着"每个Web请求",因为该类型在该容器实例的上下文中是单例.或者即使没有性能作为考虑因素,对于某些类型正常工作,您将需要比"每个Web请求"更大的生命周期(例如每个应用程序域一个实例),这更难配置.

您可能可以这样做,即使每个请求都有一个容器,但您的代码将更难维护.

容器本身是线程安全的,因此从这个意义上讲,每个请求都不需要容器实例.

在你的情况下,更糟糕的是,因为你在代码中有一个竞争条件,一个并发错误,因为你改变了ServiceLocator每个请求.该ServiceLocator存储在私有静态字段,这意味着它的注册代表是从所有线程访问.换句话说,您正在通过Web请求共享容器实例.您可能在开发期间不会注意到,因为您当时将有一个Web请求,但是当您有代码调用时ServiceLocator.Current,它将在生产中失败.例如,以下非常可能的情况,其中请求1启动并设置服务定位器.之后,请求2启动并使用它自己的容器实例覆盖服务定位器.之后,请求1中的代码调用ServiceLocator.Current现在返回第二个请求的容器.有时(如果你很幸运)你会得到一个ObjectDisposedException.如果一个线程处理其另一个线程仍在(错误地)使用的容器,则会发生这种情况.但是,有时会解析属于不同Web请求的类型(在多个线程上使用不安全的实例),这显然很糟糕并导致各种奇怪和不正确的行为.例如,当您返回LINQ to SQL DataContext或Entity Framework ObjectContext实例时.通常应根据Web请求配置这些类,并且不能由多个线程共享.