【发布时间】:2011-11-30 10:48:34
【问题描述】:
对于篇幅太长,我深表歉意,我知道对此有一些答案,但我搜索了很多,但没有找到正确的解决方案, 所以请多多包涵。
我正在尝试为遗留应用程序创建一个框架,以在 ASP.NET 网络表单中使用 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。
首先我必须创建一个处理程序工厂来覆盖我的页面的构造。 我发现这个非常好的答案可以提供帮助: 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。
我知道现在有些人会说,当你有循环依赖时,设计可能是不正确的。 首先,我不认为 Presenter 的设计是不正确的,因为它在构造函数中依赖于 View,我可以这样说 通过查看大量 MVP 实现。
有些人可能会建议将页面更改为 Presenter1 成为属性的设计,然后使用属性注入
public partial class MyPage1 : System.Web.UI.Page, IView1
{
[Dependency]
public Presenter1 Presenter
{
get; set;
}
}
有些人甚至可能建议完全删除对演示者的依赖,然后简单地通过一堆事件进行连接,但这是 不是我想要的设计,坦率地说,我不明白为什么我需要做出这个改变来适应它。
不管建议如何,还有一个问题存在:
当处理程序工厂收到页面请求时,只有一个类型可用(不是视图接口):
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);
}
问题是你现在需要为每个presenter创建一个工厂,然后调用容器 解决其他依赖关系:
class Presenter1Factory : IPresenter1Factory
{
public Presenter1Factory(Container container)
{
this._container = container;
}
public Presenter1 Create(IView1 view)
{
return new Presenter1(view, _container.Resolve<IUserRepository>,...)
}
}
这个设计看起来也很麻烦而且过于复杂,有没有人想出一个更优雅的解决方案?
【问题讨论】:
标签: c# dependency-injection ioc-container