2011-11-30 58 views
1

我對此長度表示歉意,並且我知道這裏有一些答案,但是我搜索了很多並沒有找到正確的解決方案,因此請耐心等待。C#ASP.NET依賴注入與IoC容器併發症

我想創建一個遺留應用程序框架在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的頁面:

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

現在,我可以很容易地建立我的容器中,我的作文根馬克西曼建議使用在Global.asax (這意味着雖然創建一個靜態的容器必須是線程安全的密封不能夠進一步增加註冊)

現在我可以去申報頁面

public MyPage1() : base() 
{ 
} 

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

現在我們碰到的第一個問題上的構造函數注入,我有一個循環依賴。 Presenter1取決於IView1,但頁面取決於演示者。

我知道有些人會說現在設計可能不正確,當你有循環依賴。 首先我不認爲Presenter的設計是不正確的,因爲它在構造函數中依賴於View,我可以通過查看大量的MVP實現來說這個 。

有些人可能會建議改變頁到Presenter1成爲屬性設計,然後使用屬性注入

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

有些人甚至建議取消的依賴完全主持人,然後簡單地接線,通過一串事件,但這是 不是我想要的設計,並坦率地不知道爲什麼我需要做出這種改變來適應它。

反正不管的建議,另一個問題存在:

當處理程序工廠獲取一個頁面請求只有一個類型是可用的(不是視圖interface):

Type pageType = page.GetType().BaseType; 

現在使用這種類型的你可以通過國際奧委會和它的依賴解決頁:

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

這帶來了另一個問題,大多數容器,如溫莎城堡不會讓你將此接口 重新註冊到它現在指向的實例。此外,在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

也許我誤解你的問題,但解決的辦法似乎相當簡單的對我說:促進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),但它會失敗的部分信任。所以你必須爲此調用容器。

只要在初始化階段以及某些容器即使是線程安全的(some containers甚至禁止在初始化後禁止註冊的類型)之後,您自己不手動添加註冊,所有DI容器都是線程安全的。永遠不需要這樣做(大多數容器支持的未註冊類型解析除外)。然而,隨着城堡,你需要預先註冊所有具體類型,這意味着它需要知道你的Presenter1,然後再解決它。要麼註冊這個,改變這個行爲,要麼移動到一個允許在默認情況下解析具體類型的容器。

+0

嗨史蒂文,感謝您的回答,並且您確實正確地理解了這個問題。你的解決方案有效,也許它是最好的。我試圖使它與構造函數注入一起工作。通過使視圖屬性感覺它打破了封裝,它現在作爲一個公共暴露出來,它可以被修改,因爲它沒有打算,但是現在開發人員必須在每個頁面上設置一個額外的屬性。但它看起來像這是唯一的方式,順便說一句偉大的博客文章感謝鏈接。 – Andre