C#ASP.NET依赖注入与IoC容器并发症 [英] C# ASP.NET Dependency Injection with IoC Container Complications

查看:161
本文介绍了C#ASP.NET依赖注入与IoC容器并发症的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我很抱歉,我知道这里有一些答案,但我搜索了很多,没有找到正确的解决方案,
所以请与我一起。



我正在尝试为遗留应用程序创建一个框架,以便在ASP.NET Webforms中使用DI。我可能会使用Castle Windsor
作为框架。



这些旧版应用程序将在某些地方部分使用MVP模式。



演示者将看起来像这样:

  class Presenter1 
{
public Presenter1(IView1视图,
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依赖注入



现在,我可以轻松地在我的组合根中设置我的容器,因为Mark Seeman建议使用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;组;
}
}

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



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



当处理程序工厂获取页面请求时,只有类型可用(不是查看界面):

 键入pageType = page.GetType()。BaseType; 

现在使用此类型,您可以通过IoC及其依赖关系来解析该页面:

  container.Resolve(pageType)

然后,这将知道有一个名为Presenter1的属性,并且可以注入它。
但是Presenter1需要使用IView1,但是我们从未通过容器解析出IView1,所以容器将不会知道
来提供在容器外部创建的处理程序工厂刚刚创建的具体实例。 >

所以我们需要劫持我们的处理程序工厂,并替换视图界面:
所以处理程序工厂解决页面的位置:

  private void InjectDependencies(对象页)
{
键入pageType = page.GetType()。BaseType;
// hack
foreach(pageType.GetInterfaces()中的var intf)
{
if(typeof(IView).IsAssignableFrom(intf))
{
_container.Bind(intf,()=> page);
}
}

// injectDependencies to page ...
}

这是另一个问题,大多数容器如Castle Windsor将不允许您将此界面
重新注册到现在指向的实例。此外,在Global.asax中注册的容器也不是线程安全的,因为这个容器应该只在这个时候被读取。



另一个解决方案是创建一个在每个Web请求上重建容器的功能,然后检查
如果容器包含组件IView(如果未设置实例)。但是这似乎是浪费的,反而建议使用。



另一个解决方案是创建一个名为
IPresenterFactory的特殊工厂,并将依赖关系放在页面构造函数中: / p>

  public MyPage1(IPresenter1Factory factory):this()
{
this._presenter = factory.Create这个);
}

问题是您现在需要为每位主持人创建一个工厂,然后调用容器
以解决其他依赖关系:

  class Presenter1Factory:IPresenter1Factory 
{
public Presenter1Factory(Container container)
{
this._container = container;
}
public Presenter1 Create(IView1 view)
{
return new Presenter1(view,_container.Resolve< IUserRepository>,...)
}
}

这个设计似乎很麻烦,过于复杂,有没有人想到一个更优雅的解决方案?

解决方案

也许我误解你的问题,但解决方案似乎对我来说很简单:推广 Presenter1 上的某个财产:

  class Presenter1 
{
public Presenter1(IRepository< User> userRepository)
{
}

public IView1 View {get;组; }
}

这样你可以在这个视图中设置演示者: p>

  public Presenter1 Presenter {get;组; } 

public MyPage1()
{
ObjectFactory.BuildUp(this);
this.Presenter.View = this;
}

或者没有属性注入,你可以这样做:

  private Presenter1 _presenter; 

public MyPage1()
{
this._presenter = ObjectFactory.Resolve< Presenter1&();
this._presenter.View = this;
}

页面中的构造函数注入类和用户控件将永远无法正常工作。您可以充分信任(,如本文所示),但会部分信任失败。所以你必须调用这个容器。



所有的DI容器都是线程安全的,只要你不要在初始化阶段之后自己手动添加注册,有些容器即使是线程安全的(一些容器甚至在初始化后禁止注册类型)。永远不需要这样做(除了大多数容器支持的未注册的类型解析)。然而,使用城堡,您需要先注册所有具体类型,这意味着在解决之前需要了解您的 Presenter1 。或者注册这个,改变这种行为,或者移动到允许默认情况下解析具体类型的容器。


I apologise for the length, and I know there are some answers on this but I searched a lot and haven't found the right solution, so please bear with me.

I am trying to create a framework for legacy applications to use DI in ASP.NET webforms. I will probably use Castle Windsor as the framework.

These legacy applications will use in part an MVP pattern in some places.

A presenter would look something like this:

class Presenter1
{
    public Presenter1(IView1 view, 
        IRepository<User> userRepository)
    {
    }
}

Now the ASP.NET Page would look something like this:

public partial class MyPage1 : System.Web.UI.Page, IView1
{
    private Presenter1 _presenter;
}

Before using DI I would instantiate the Presenter as follows in the OnInit of the page:

protected override void OnInit(EventArgs e)
{
    base.OnInit(e);
    _presenter = new Presenter1(this, new UserRepository(new SqlDataContext()));
}

So now I want to use DI.

First I must create a handler factory to override the construction of my page. I found THIS really good answer to help: How to use Dependency Injection with ASP.NET

Now I can easily set up my containers in my composition root as Mark Seeman suggest to use the Global.asax (This means though to create a static container that must be thread safe and sealed not to be able to add further registrations)

Now I can go and declare the constructor injection on the page

public MyPage1() : base()
{
}

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

Now we run into the first problem, I have a circular dependency. Presenter1 depends on IView1, But the page depends on the presenter.

I know what some will say now that the design is probably incorrect when you have circular dependencies. First I dont think the Presenter design is incorrect, by it taking a dependency in the constructor to the View, and I can say this by looking at plenty of MVP implementations.

Some may suggest changing the Page to a design where Presenter1 becomes a property and then using Property injection

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

Some may even suggest removing the dependency to presenter completely and then simply Wiring up via a bunch of events, But this is not the design I wanted and frankly dont see why I need to make this change to accomodate it.

Anyway regardless of the suggestion, another problem exists:

When the Handler factory gets a page request only a type is available (NOT THE VIEW INTERFACE):

Type pageType = page.GetType().BaseType;

now using this type you can resolve the Page via IoC and its dependencies:

container.Resolve(pageType)

This will then know that there is a property called Presenter1 and be able to inject it. But Presenter1 needs IView1, but we never resolved IView1 through the container, so the container won't know to provide the concrete instance the handler factory just created as it was created outside of container.

So we need to hack our handler factory and replace the view interface: So where the handler factory resolves the page:

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

This poses another problem, most containers like Castle Windsor will not allow you to reregister this interface to the instance it is pointing to now. Also with the container being registered in the Global.asax, it is not thread-safe to do as the container should be read only at this point.

The other solution is to create a function to rebuild the container on each web request, and then check to see if the container contains the component IView if not set the instance. But this seems wasteful and goes against suggested use.

The other solution is to create a special Factory called IPresenterFactory and put the dependency in the page constructor:

public MyPage1(IPresenter1Factory factory) : this()
{
    this._presenter = factory.Create(this);
}

The problem is that you now need to create a factory for each presenter and then make a call to the container to resolve other dependencies:

class Presenter1Factory : IPresenter1Factory 
{
    public Presenter1Factory(Container container)
    {
        this._container = container;
    }
    public Presenter1 Create(IView1 view)
    {
        return new Presenter1(view, _container.Resolve<IUserRepository>,...)
    }
}

This design also seems cumbersome and over complicated, does any one have ideas for a more elegant solution?

解决方案

Perhaps I misunderstand your problems, but the solution seems fairly simple to me: promote the IView to a property on the Presenter1:

class Presenter1
{
    public Presenter1(IRepository<User> userRepository)
    {
    }

    public IView1 View { get; set; }            
}

This way you can set the presenter on the view like this:

public Presenter1 Presenter { get; set; }

public MyPage1() 
{
    ObjectFactory.BuildUp(this);
    this.Presenter.View = this;
}

Or without property injection, you can do it as follows:

private Presenter1 _presenter;

public MyPage1() 
{
    this._presenter = ObjectFactory.Resolve<Presenter1>();
    this._presenter.View = this;
}

Constructor injection in Page classes and user controls will never really work. You can get it to work in full trust (as this article shows), but it will fail in partial trust. So you will have to call the container for this.

All DI containers are thread-safe, as long as you don't manually add registrations yourself after the initialization phase and with some containers even that is thread-safe (some containers even forbid registering types after initialization). There would never be a need to do this (except for unregistered type resolution, which most containers support). With Castle however, you need to register all concrete types upfront, which means it needs to know about your Presenter1, before you resolve it. Either register this, change this behavior, or move to a container that allows resolving concrete types by default.

这篇关于C#ASP.NET依赖注入与IoC容器并发症的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆