[英]Why should I use IoC Container (Autofac, Ninject, Unity etc) for Dependency Injection in ASP.Net Applications?
[英]C# ASP.NET Dependency Injection with IoC Container Complications
很抱歉,我对此表示歉意,但我知道有一些答案,但是我进行了很多搜索,但没有找到正确的解决方案,因此请耐心等待。
我正在尝试为遗留应用程序创建一个框架,以在ASP.NET Web表单中使用DI。 我可能会使用温莎城堡作为框架。
这些遗留应用程序将在某些地方部分使用MVP模式。
演示者将如下所示:
class Presenter1
{
public Presenter1(IView1 view,
IRepository<User> userRepository)
{
}
}
现在,ASP.NET页面将如下所示:
public partial class MyPage1 : System.Web.UI.Page, IView1
{
private Presenter1 _presenter;
}
在使用DI之前,我将在页面的OnInit中实例化Presenter:
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
_presenter = new Presenter1(this, new UserRepository(new SqlDataContext()));
}
所以现在我要使用DI。
首先,我必须创建一个处理程序工厂以覆盖页面的构造。 我发现这确实是一个很好的答案,可以帮助您: 如何在ASP.NET Web窗体中使用依赖注入
现在,正如Mark Seeman建议使用Global.asax一样,我可以在我的合成根目录中轻松地设置容器(这意味着尽管创建了一个静态容器,该容器必须是线程安全的并且密封的,才能添加更多的注册信息)
现在我可以在页面上声明构造函数注入
public MyPage1() : base()
{
}
public MyPage1(Presenter1 presenter) : this()
{
this._presenter = presenter;
}
现在我们遇到第一个问题,我有一个循环依赖性。 Presenter1取决于IView1,但是页面取决于Presenter。
我知道现在有些人会说,当您具有循环依赖项时,该设计可能不正确。 首先,我不认为Presenter设计是不正确的,因为它将构造函数中的依赖关系带给了View,我可以通过查看大量MVP实现来说明这一点。
有些人可能建议将Page更改为Presenter1成为属性的设计,然后使用属性注入
public partial class MyPage1 : System.Web.UI.Page, IView1
{
[Dependency]
public Presenter1 Presenter
{
get; set;
}
}
有些人甚至建议完全删除对演示者的依赖,然后通过一系列事件简单地进行布线。但这不是我想要的设计,坦白地说,我不明白为什么我需要进行此更改以适应它。
无论如何建议,仍然存在另一个问题:
当处理程序工厂获得页面请求时,只有一种类型可用(没有视图接口):
Type pageType = page.GetType().BaseType;
现在使用此类型,您可以通过IoC及其依赖项来解析Page:
container.Resolve(pageType)
然后,它将知道存在一个名为Presenter1的属性并能够注入它。 但是Presenter1需要IView1,但我们从未通过容器解析IView1,因此容器将不知道提供处理程序工厂刚创建的具体实例,因为它是在容器外部创建的。
因此,我们需要修改处理程序工厂并替换视图接口:处理程序工厂解析页面的位置:
private void InjectDependencies(object page)
{
Type pageType = page.GetType().BaseType;
// hack
foreach (var intf in pageType.GetInterfaces())
{
if (typeof(IView).IsAssignableFrom(intf))
{
_container.Bind(intf, () => page);
}
}
// injectDependencies to page...
}
这带来了另一个问题,大多数城堡(如Castle Windsor)将不允许您将此接口重新注册到它现在指向的实例。 同样,在Global.asax中注册了容器的情况下,这样做也不是线程安全的,因为此时只能读取该容器。
另一个解决方案是创建一个函数,以在每个Web请求上重建容器,然后检查容器(如果未设置实例)是否包含组件IView。 但这似乎很浪费,并且与建议的用法背道而驰。
另一种解决方案是创建一个名为IPresenterFactory的特殊工厂,并将依赖项放入页面构造函数中:
public MyPage1(IPresenter1Factory factory) : this()
{
this._presenter = factory.Create(this);
}
问题是您现在需要为每个演示者创建一个工厂,然后调用该容器以解决其他依赖项:
class Presenter1Factory : IPresenter1Factory
{
public Presenter1Factory(Container container)
{
this._container = container;
}
public Presenter1 Create(IView1 view)
{
return new Presenter1(view, _container.Resolve<IUserRepository>,...)
}
}
这种设计似乎也很繁琐,是否有人对更优雅的解决方案有想法?
也许我误解了您的问题,但是解决方案对我来说似乎很简单:将IView
提升为Presenter1
上的一个属性:
class Presenter1
{
public Presenter1(IRepository<User> userRepository)
{
}
public IView1 View { get; set; }
}
这样,您可以像这样在视图上设置演示者:
public Presenter1 Presenter { get; set; }
public MyPage1()
{
ObjectFactory.BuildUp(this);
this.Presenter.View = this;
}
或不使用属性注入,可以按照以下步骤进行操作:
private Presenter1 _presenter;
public MyPage1()
{
this._presenter = ObjectFactory.Resolve<Presenter1>();
this._presenter.View = this;
}
Page
类和用户控件中的构造方法注入将永远无法真正起作用。 您可以使它在完全信任下工作( 如本文所示 ),但是在部分信任下它将失败。 因此,您必须为此调用容器。
所有DI容器都是线程安全的,只要您没有在初始化阶段之后自己手动添加注册,甚至不使用线程安全的一些容器 ( 某些容器甚至在初始化后禁止注册类型)。 永远不需要这样做(大多数容器支持的未注册类型解析除外)。 但是,对于Castle,您需要预先注册所有具体类型,这意味着在解决它之前,它需要了解Presenter1
。 注册,更改此行为或移至默认情况下允许解析具体类型的容器。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.