我为这个长度道歉,我知道有一些答案,但我搜索了很多,但没有找到正确的解决方案, 所以请耐心等待。
我正在尝试为遗留应用程序创建一个框架,以便在ASP.NET webforms中使用DI。我可能会使用Castle Windsor 作为框架。
这些遗留应用程序在某些地方将部分使用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。
首先,我必须创建一个处理程序工厂来覆盖我的页面构造。 我找到了这个非常好的答案: How to use Dependency Injection with ASP.NET Web Forms
现在,我可以轻松地在我的合成根中设置我的容器,因为Mark Seeman建议使用Global.asax (这意味着虽然要创建一个必须是线程安全且密封的静态容器,但不能再添加注册)
现在我可以在页面上声明构造函数注入
public MyPage1() : base()
{
}
public MyPage1(Presenter1 presenter) : this()
{
this._presenter = presenter;
}
现在我们遇到第一个问题,我有一个循环依赖。 Presenter1依赖于IView1,但页面取决于演示者。
我知道有些人会说,当你有循环依赖时,设计可能是不正确的。 首先,我不认为Presenter设计是不正确的,它将构造函数中的依赖关系转换为View,我可以这样说 通过观察大量的MVP实现。
有些人可能会建议将Page更改为Presenter1成为属性的设计,然后使用Property injection
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并将依赖项放在页面构造函数中:
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>,...)
}
}
这种设计看起来既麻烦又复杂,是否有人想要更优雅的解决方案呢?
答案 0 :(得分:1)
也许我误解了你的问题,但解决方案对我来说似乎相当简单:将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
类和用户控件中的构造函数注入永远不会真正起作用。您可以让它完全信任(as this article shows),但它会在部分信任中失败。所以你必须为此调用容器。
所有DI容器都是线程安全的,只要您在初始化阶段之后不自己手动添加注册,并且某些容器甚至是线程安全的(some containers甚至禁止在初始化后注册类型)。永远不需要这样做(除了未注册的类型解析,大多数容器都支持)。但是,对于Castle,您需要预先注册所有具体类型,这意味着在解决之前需要了解您的Presenter1
。可以注册它,更改此行为,也可以移动到允许默认解析具体类型的容器。