C# ASP.NET 依赖注入与 IoC 容器并发症
对于篇幅,我深表歉意,我知道对此有一些答案,但我搜索了很多,但没有找到正确的解决方案, 所以请耐心听我说。
我正在尝试为遗留应用程序创建一个框架,以便在 ASP.NET Web 表单中使用 DI。我可能会使用温莎城堡 作为框架。
这些遗留应用程序将在某些地方部分使用 MVP 模式。
Presenter 看起来像这样:
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 实现。
有些人可能建议将页面更改为 Presenter1 成为属性的设计,然后使用属性注入
public partial class MyPage1 : System.Web.UI.Page, IView1
{
[Dependency]
public Presenter1 Presenter
{
get; set;
}
}
有些人甚至可能建议完全删除对 Presenter 的依赖,然后简单地通过一堆事件进行连接,但这是 这不是我想要的设计,坦率地说,我不明白为什么我需要做出这样的改变来适应它。
无论如何,无论建议如何,都存在另一个问题:
当处理程序工厂获取页面请求时,只有一种类型可用(不是视图界面):
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>,...)
}
}
这种设计似乎也很麻烦且过于复杂,有人有更优雅的解决方案的想法吗?
I apologise for the length, and I know there are some answers on this but I searched a lot and haven't found the right solution,
so please bear with me.
I am trying to create a framework for legacy applications to use DI in ASP.NET webforms. I will probably use Castle Windsor
as the framework.
These legacy applications will use in part an MVP pattern in some places.
A presenter would look something like this:
class Presenter1
{
public Presenter1(IView1 view,
IRepository<User> userRepository)
{
}
}
Now the ASP.NET Page would look something like this:
public partial class MyPage1 : System.Web.UI.Page, IView1
{
private Presenter1 _presenter;
}
Before using DI I would instantiate the Presenter as follows in the OnInit of the page:
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
_presenter = new Presenter1(this, new UserRepository(new SqlDataContext()));
}
So now I want to use DI.
First I must create a handler factory to override the construction of my page.
I found THIS really good answer to help:
How to use Dependency Injection with ASP.NET Web Forms
Now I can easily set up my containers in my composition root as Mark Seeman suggest to use the Global.asax
(This means though to create a static container that must be thread safe and sealed not to be able to add further registrations)
Now I can go and declare the constructor injection on the page
public MyPage1() : base()
{
}
public MyPage1(Presenter1 presenter) : this()
{
this._presenter = presenter;
}
Now we run into the first problem, I have a circular dependency.
Presenter1 depends on IView1, But the page depends on the presenter.
I know what some will say now that the design is probably incorrect when you have circular dependencies.
First I dont think the Presenter design is incorrect, by it taking a dependency in the constructor to the View, and I can say this
by looking at plenty of MVP implementations.
Some may suggest changing the Page to a design where Presenter1 becomes a property and then using Property injection
public partial class MyPage1 : System.Web.UI.Page, IView1
{
[Dependency]
public Presenter1 Presenter
{
get; set;
}
}
Some may even suggest removing the dependency to presenter completely and then simply Wiring up via a bunch of events, But this is
not the design I wanted and frankly dont see why I need to make this change to accomodate it.
Anyway regardless of the suggestion, another problem exists:
When the Handler factory gets a page request only a type is available (NOT THE VIEW INTERFACE):
Type pageType = page.GetType().BaseType;
now using this type you can resolve the Page via IoC and its dependencies:
container.Resolve(pageType)
This will then know that there is a property called Presenter1 and be able to inject it.
But Presenter1 needs IView1, but we never resolved IView1 through the container, so the container won't know
to provide the concrete instance the handler factory just created as it was created outside of container.
So we need to hack our handler factory and replace the view interface:
So where the handler factory resolves the page:
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...
}
This poses another problem, most containers like Castle Windsor will not allow you to reregister this interface
to the instance it is pointing to now. Also with the container being registered in the Global.asax, it is not thread-safe to
do as the container should be read only at this point.
The other solution is to create a function to rebuild the container on each web request, and then check to see
if the container contains the component IView if not set the instance. But this seems wasteful and goes against suggested use.
The other solution is to create a special Factory called
IPresenterFactory and put the dependency in the page constructor:
public MyPage1(IPresenter1Factory factory) : this()
{
this._presenter = factory.Create(this);
}
The problem is that you now need to create a factory for each presenter and then make a call to the container
to resolve other dependencies:
class Presenter1Factory : IPresenter1Factory
{
public Presenter1Factory(Container container)
{
this._container = container;
}
public Presenter1 Create(IView1 view)
{
return new Presenter1(view, _container.Resolve<IUserRepository>,...)
}
}
This design also seems cumbersome and over complicated, does any one have ideas for a more elegant solution?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
也许我误解了你的问题,但解决方案对我来说似乎相当简单:将
IView
提升为Presenter1
上的属性:这样你就可以在视图上设置演示者,例如this:
或者在没有属性注入的情况下,您可以按如下方式执行:
Page
类和用户控件中的构造函数注入永远不会真正起作用。您可以让它在完全信任的情况下工作(如本文所示),但在部分信任中会失败。所以你必须为此调用容器。所有 DI 容器都是线程安全的,只要您在初始化阶段之后不自己手动添加注册,并且对于某些容器来说,即使这是线程安全的 (一些容器甚至禁止在初始化后注册类型)。永远不需要这样做(除了大多数容器支持的未注册类型解析)。然而,对于 Castle,您需要预先注册所有具体类型,这意味着它需要在解析之前了解您的
Presenter1
。要么注册这个,改变这个行为,要么移动到一个默认允许解析具体类型的容器。Perhaps I misunderstand your problems, but the solution seems fairly simple to me: promote the
IView
to a property on thePresenter1
:This way you can set the presenter on the view like this:
Or without property injection, you can do it as follows:
Constructor injection in
Page
classes and user controls will never really work. You can get it to work in full trust (as this article shows), but it will fail in partial trust. So you will have to call the container for this.All DI containers are thread-safe, as long as you don't manually add registrations yourself after the initialization phase and with some containers even that is thread-safe (some containers even forbid registering types after initialization). There would never be a need to do this (except for unregistered type resolution, which most containers support). With Castle however, you need to register all concrete types upfront, which means it needs to know about your
Presenter1
, before you resolve it. Either register this, change this behavior, or move to a container that allows resolving concrete types by default.