繁体   English   中英

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

[英]C# ASP.NET Dependency Injection with IoC Container Complications

很抱歉,我对此表示歉意,但我知道有一些答案,但是我进行了很多搜索,但没有找到正确的解决方案,因此请耐心等待。

我正在尝试为遗留应用程序创建一个框架,以在ASP.NET Web表单中使用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。

首先,我必须创建一个处理程序工厂以覆盖页面的构造。 我发现这确实是一个很好的答案,可以帮助您: 如何在ASP.NET Web窗体中使用依赖注入

现在,正如Mark Seeman建议使用Global.asax一样,我可以在我的合成根目录中轻松地设置容器(这意味着尽管创建了一个静态容器,该容器必须是线程安全的并且密封的,才能添加更多的注册信息)

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

public MyPage1() : base()
{
}

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

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

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

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

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

有些人甚至建议完全删除对演示者的依赖,然后通过一系列事件简单地进行布线。但这不是我想要的设计,坦白地说,我不明白为什么我需要进行此更改以适应它。

无论如何建议,仍然存在另一个问题:

当处理程序工厂获得页面请求时,只有一种类型可用(没有视图接口):

Type pageType = page.GetType().BaseType;

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

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

这种设计似乎也很繁琐,是否有人对更优雅的解决方案有想法?

也许我误解了您的问题,但是解决方案对我来说似乎很简单:将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类和用户控件中的构造方法注入将永远无法真正起作用。 您可以使它在完全信任下工作( 如本文所示 ),但是在部分信任下它将失败。 因此,您必须为此调用容器。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM