2011-11-30 47 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