编写以下代码是否正常:
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();
}
}
或者为每个请求注册依赖项是不好的做法?
感谢。
答案 0 :(得分:5)
注意:以下建议适用于所有IoC框架,并非针对Unity。
TLDR :不要这样做。在Application_Start
中注册一次容器,只需拨打ServiceLocator.SetLocatorProvider
一次。
可以将依赖项注册为“每个请求”或“每个Web请求”生命周期。但是,每个Web请求创建一个新的容器实例是 非常糟糕的 练习。
容器被优化用于解析实例(不用于注册),这通常意味着当第一次请求类型(从容器中)时,会有一些动态代码生成。注册和代码生成和编译过程可能非常耗时,并且在每个Web请求上创建容器的新实例将重新触发完整的注册和代码生成过程。这需要时间,生成许多临时对象(要收集更多垃圾),并生成必须至少在该容器实例的生命周期内缓存的新代码。
此外,拥有多个容器实例会使其他优化变得困难,例如注册范围大于每个Web请求的实例。在您的情况下,将类型注册为“singleton”实际上意味着“每个Web请求”,因为该类型在该容器实例的上下文中是单例。或者即使没有考虑性能,要使某些类型正常工作,您需要的生命周期大于“每个网络请求”(例如每个应用程序域一个实例),这更难配置。
你可能会这样做,即使每个请求都有一个容器,但你的代码将更难维护。
容器本身是线程安全的,因此从这个意义上讲,每个请求都不需要容器实例。
在您的情况下,由于您更改了ServiceLocator
,因此您的代码中存在竞争条件,并发错误,这更糟糕在每个请求上。 ServiceLocator
将注册的委托存储在私有静态字段中,这意味着可以从所有线程访问它。换句话说,您正在通过Web请求共享容器实例。您可能在开发期间不会注意到,因为您当时将有一个Web请求,但是当您调用ServiceLocator.Current
代码时,它将在生产中失败。例如,以下非常可能的情况,其中请求1启动并设置服务定位器。之后,请求2启动并使用它自己的容器实例覆盖服务定位器。之后,请求1中的代码调用ServiceLocator.Current
,现在返回第二个请求的容器。有时候(如果你很幸运)你会得到ObjectDisposedException
。如果一个线程处理其另一个线程仍在(错误地)使用的容器,则会发生这种情况。但有时会解析属于不同Web请求的类型(在多个线程上使用不安全的实例),这显然很糟糕并导致各种奇怪和不正确的行为。例如,当您返回LINQ to SQL DataContext
或Entity Framework ObjectContext
实例时。通常应根据Web请求和can't be shared by multiple threads配置这些类。