使用IoC容器并发症的C#ASP.NET依赖注入

时间:2011-11-30 10:48:35

标签: c# dependency-injection ioc-container

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

我正在尝试为遗留应用程序创建一个框架,以便在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>,...)
    }
}

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

1 个答案:

答案 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。可以注册它,更改此行为,也可以移动到允许默认解析具体类型的容器。