IoC容器并发症的C#ASP.NETdependency injection

我为这个长度道歉,我知道这里有一些答案,但我搜索了很多,但没有找到正确的解决方案,所以请耐心等待。

我正在尝试为遗留应用程序创建一个框架,以便在ASP.NET webforms中使用DI。 我可能会使用Castle Windsor作为框架。

这些遗留应用程序将在某些地方部分使用MVP模式。

演示者看起来像这样:

class Presenter1 { public Presenter1(IView1 view, IRepository 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窗体中使用dependency injection

现在我可以轻松地在我的组合根中设置我的容器,因为Mark Seeman建议使用Global.asax(这意味着创建一个必须是线程安全且密封的静态容器,不能再添加注册)

现在我可以在页面上声明构造函数注入

 public MyPage1() : base() { } public MyPage1(Presenter1 presenter) : this() { this._presenter = presenter; } 

现在我们遇到第一个问题,我有一个循环依赖。 Presenter1依赖于IView1,但页面取决于演示者。

我知道有些人会说,当你有循环依赖时,设计可能是错误的。 首先,我不认为Presenter设计是不正确的,因为它将构造函数中的依赖关系转换为View,我可以通过查看大量的MVP实现来说明这一点。

有些人可能会建议将Page更改为Presenter1成为属性的设计,然后使用Property注入

 public partial class MyPage1 : System.Web.UI.Page, IView1 { [Dependency] public Presenter1 Presenter { get; set; } } 

有些人甚至可能建议完全删除对演示者的依赖,然后简单地通过一堆事件接线,但这不是我想要的设计,坦率地说不明白为什么我需要做出这个改变来容纳它。

无论如何,不​​管有什么建议,都存在另一个问题:

当Handler工厂获取页面请求时,只有一种类型可用(不是视图界面):

 Type pageType = page.GetType().BaseType; 

现在使用此类型,您可以通过IoC及其依赖项解析页面:

 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的特殊Factory,并将依赖项放在页面构造函数中:

 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,...) } } 

这种设计看起来既麻烦又复杂,是否有人想要更优雅的解决方案呢?

也许我误解了你的问题,但对我来说解决方案似乎相当简单:将IView提升为Presenter1上的属性:

 class Presenter1 { public Presenter1(IRepository 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(); this._presenter.View = this; } 

Page类和用户控件中的构造函数注入永远不会真正起作用。 你可以让它完全信任( 如本文所示 ),但它会在部分信任中失败。 所以你必须为此调用容器。

所有DI容器都是线程安全的,只要您在初始化阶段之后不自己手动添加注册,并且某些容器甚至是线程安全的( 某些容器甚至禁止在初始化后注册类型)。 永远不需要这样做(除了未注册的类型解析,大多数容器都支持)。 但是,对于Castle,您需要预先注册所有具体类型,这意味着在解决之前需要了解Presenter1 。 注册此项,更改此行为,或移至允许默认情况下解析具体类型的容器。