簡體   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