如何自动注入所有子对象

时间:2010-06-30 08:35:40

标签: c# asp.net inversion-of-control ioc-container spring.net

考虑这个抽象类

public abstract class Foo
{
    public Injectable Prop {get;set;}
}

我有一个应用程序,我想要增强和同时重构简单。 我有超过100个类调用一些相同的东西(例如,类Injectable),我认为这个行为可以被抽象并设置为基类,并且每个人都将从这个基类继承,以便删除copy /粘贴代码。

但是,我想避免在spring配置文件中复制/粘贴代码,方法是定义一个Injectable对象,然后定义一个子对象foreach和每个继承自Foo的类。

我正在寻找一种方法来设置抽象类的属性,然后所有子元素通过配置自动获取它们。我想避免像这样制作抽象类:

public abstract class Foo
{
    public Injectable Prop 
    { 
       get { return (Injectable)ContextRegistry.GetContext()["Injectable"]; }
    }
}

感谢任何建议

编辑: 为了让事情变得更复杂,子类是ASP.NET页面,所以我对它们的生成方式的控制有限。

目前我正在使用上面提到的代码,其中抽象类使用Id“Injectable”引用DI创建的对象。我想避免飞行字符串

更新(使用解决方案)

考虑: 类

public abstract class BasePage : System.Web.UI.Page
{
   public IInjectable FooProp {get;set;}
}

public abstract class BaseControl : System.Web.UI.UserControl
{
   public IInjectable FooProp {get;set;}
}

public partial class ChildPage : BasePage
{
   protected void Page_Load(object sender, EventArgs e)
   {
       FooProp.DoSomeThing();
   }
}

public partial class ChildControl : BaseControl
{
   protected void Page_Load(object sender, EventArgs e)
   {
       FooProp.DoSomeThing();
   }
}
春天cfg ...

<object id="Injectable" type="ConcreteInjectable">
    <property name="SomeProp" value="Injected!!" />
</object>
<!--The requirement is to declare something like:-->
<object type="BasePage" abstract="true">
  <property name="FooProp" ref="Injectable />
</object>
<!--it works for usercontrols too-->
<object type="BaseControl" abstract="true">
  <property name="FooProp" ref="Injectable />
</object>

并且效果将是BasePage的每个继承者将FooProp属性注入我配置的内容。

如果使用某种约定绑定可以实现这一点很重要,但我不想使用字符串并在我的代码中使用DI引用。

第二次更新和解决方案 感谢tobsenErich Eichinger找到了解决方案: 首先,这本身并不支持,但解决方案不是很难看,也不会以糟糕的方式打破DI模式规范

现在,上面给出了所需的所有弹簧配置(在更新中) Erich的解决方案就是这样,像IHttpModule一样:

public class PageModuleInjecter : IHttpModule
{
  public void Dispose() {}

  public void Init(HttpApplication context) {
     context.PreRequestHandlerExecute += context_PreRequestHandlerExecute;
  }

  void context_PreRequestHandlerExecute(object sender, EventArgs e) {
    IHttpHandler handler = ((HttpApplication )sender).Context.Handler;
    if (handler is BasePage)
        Spring.Context.Support.WebApplicationContext.Current
           .ConfigureObject(handler, typeof(BasePage).FullName);
  }
}

就是这样! (别忘了像往常一样在web.config中注册模块)

为了寻找功能(也许是优雅),我发现不是使用IHttpModule(我不想添加另一个类),你可以声明BasePage这样

public abstract class BasePage : System.Web.UI.Page
{
   public IInjectable FooProp {get;set;}

   protected override OnPreInit(EventArgs e)
   {
       Spring.Context.Support.WebApplicationContext.Current
           .ConfigureObject(this, typeof(BasePage).FullName);
       base.OnPreInit(e);
   }
}

它就像一个魅力而不需要任何添加的模块等。

很高兴这也适用于UserControls(虽然生命周期中的事件不同,因为用户控件不存在OnPreInit):

public abstract class BaseControl : System.Web.UI.UserControl
{
   public IInjectable FooProp {get;set;}

   protected override OnInit(EventArgs e)
   {
       Spring.Context.Support.WebApplicationContext.Current
          .ConfigureObject(this, typeof(BaseControl).FullName);
       base.OnInit(e);
   }
}

感谢收看!

5 个答案:

答案 0 :(得分:3)

抱歉,我只有有限的时间,如果以下描述太短/抽象,请告诉我

首先,目前还没有这样的功能。但是只需几行代码,您就可以自己完成。

总的来说,我将追求将特定类映射到用于配置实例的对象定义的想法。

的内容
if (object is MyBaseClass)
     applicationContext.Configure(object, "myObjectDefinitionName");

以下是解决方案的概要: 由于您的问题与ASP.NET WebForms相关,因此HttpModule是一个很好的起点,可以挂钩创建和配置.aspx页面。 我们的想法是编写自己的IHttpModule,它在执行之前执行页面配置。基本上你只需要

public class MyBaseClassConfigurationModule : IHttpModule {

  private System.Collections.Generic.Dictionary<Type, String> typeObjectDefinitionMap;

  public void Dispose() {}

  public void Init(HttpApplication context) {
     context.PreRequestHandlerExecute += context_PreRequestHandlerExecute;
  }

  void context_PreRequestHandlerExecute(object sender, EventArgs e) {
    IHttpHandler handler = ((HttpApplication )sender).Context.Handler;
    foreach(Type t in typeObjectDefinitionMap.Keys) {
        if (t.IsAssignableFrom(app.Context.Handler.GetType)) {
            Spring.Context.Support.WebApplicationContext.Current
              .ConfigureObject(handler, typeObjectDefinitionMap[t]);
        }
    }
  }
}

并根据22.4.2. Injecting dependencies into custom HTTP modules配置您的模块。

HTH, 埃里希

答案 1 :(得分:2)

您可以使用自动装配(请参阅spring.net文档中的5.3.6. Autowiring collaborators),通过spring自动设置Injectable-Property,而不会让班级知道任何关于spring的信息。

你写道“我想避免像这样制作抽象类”,这是一个明智的选择,因为你不应该谈论控制容器的反转。

我想知道谁负责创建源自Foo的类的实例? 我认为你应该让spring创建从Foo派生的Asp.Net页面类的对象。看来Spring对Asp.net页面(Spring.Web中的Spring.Web.Support.PageHandlerFactory,Spring.Web.Services.WebServiceHandlerFactory等)提供了广泛的支持 - 我会发布一个适合您需求的示例配置,但我没有使用ASP.Net了。所以这里有一些链接:

您还应该查看作为源代码分发的一部分的spring web示例。

== BeginEdit ==

为了回答这个问题,我需要知道决定逻辑对于孩子的实例化是什么。即何时/何地是由谁请求的具体儿童对象?谁决定要创建的对象的具体类型? 如果您可以控制具体子类的实例化,则可以创建具体类型的实例,并告诉spring以后设置属性:

  

void ConfigureObject(对象目标):将依赖项注入提供的目标实例。抽象对象定义的名称是目标实例的System.Type.FullName。当在开发人员控制之外实例化对象时,通常使用此方法,例如,当ASP.NET实例化Web控件时以及WinForms应用程序创建UserControls时。

     

void ConfigureObject(对象目标,字符串名称):提供与先前列出的Configure方法相同的功能,但使用命名对象定义而不是使用类型的全名。

这将保留您的对象POCO,并且您只有一个对象创建点具有spring的依赖性。

如果逻辑可以通过页面请求的正则表达式来解析要实例化的类型,那么Spring也有一个解决方案:22.4.3. Injecting dependencies into HTTP handlers and handler factories。但是你必须告诉spring哪个objectdefinition要实例化,并且似乎有一个自动化可能对你的情况有用(取决于你的子类的实例化的逻辑)(也是1.3 spring文档的22.4.3):

  

Spring的DefaultHandlerFactory使用 .NET类System.Web.UI.SimpleHandlerFactory 创建处理程序实例,使用名称与请求URL的文件名匹配的对象定义来配置每个实例。 DemoHandler.ashx的抽象对象定义就是这种方法的一个例子。您还可以配置实现IHttpHandler接口的标准类,如上面MyCustomHttpHandler类的示例所示。

== EndEdit中==

== Begin2ndEdit ==

似乎是Erich Eichinger(spring.net开发者之一)once had exactly the same problem as you do。似乎最后他不得不像你一样和容器交谈。正如Erich在论坛帖子中写道的那样,他不太乐意直接依赖IoC Container。也许他提出的解决方案已经集成到Spring中。我会试着找出来。

== End2ndInit ==

答案 2 :(得分:0)

我认为你提出的是一个从配置文件中获取属性列表的类。如果是这样,您应该查看Code Generators。您可以创建一个简单的代码生成器来读取配置文件并在给定的子类中创建属性。 Code Smith应该是一个好的开始。

答案 3 :(得分:0)

大多数依赖注入框架允许通过“约定”进行注入绑定。例如,您可以在属性上放置一个属性来标记它以进行注入,当DI框架构造一个类时,它将知道向该属性注入一个值。

public abstract class Foo
{
    [Inject]
    public Bar Bar {get; set;}
}

public class Baz : Foo
{
    ...
}

public class SomeUtil
{
    Baz _baz;
    public SomeUtil(Baz baz)
    {
        _baz = baz;
    }
}

在上面的示例中,如果我们可以假设SomeUtil类是通过依赖注入生成的,那么DI框架可以配置为生成Baz并填充其Bar属性。

我不知道Spring的具体实现,但这是我要看的总体方向。

更新

当我谈到“约定”的约束时,我指的是自动布线。 Tobsen的回答对Spring的自动布线有一些很好的参考。它似乎比Ninject更难,这是我一直在使用的。但是,查看这些文档,您可以告诉框架自动将值注入具有指定名称或类型的任何公共属性。在上面的例子中,您可以告诉它应该注入任何名为“Bar”的属性。由于Foo的所有子类都包含一个名为Bar的属性,因此它们都会注入此值。这需要对配置文件进行一次性更改,之后的所有内容都应该“正常工作”。

但是,我得到的印象是,问题的真正根源在于您没有以正确的方式使用依赖注入。托宾也接受了这一点。如果你说new Baz(),这是依赖注入的反模式。相反,你的依赖关系应该涓涓细流到代码中可能的最高点(DI书称之为“上下文根”,你实际上会有代码要求你需要的依赖。像这样:

public static void Main()
{
    var someUtil = (SomeUtil)ContextRegistry.GetContext()["SomeUtil"];
    someUtil.DoSomething();
}

在弄清楚如何构造SomeUtil的过程中,Spring会发现它首先需要一个Baz。它会注意到Baz上有一个Bar属性,所以它会创建一个Bar并将其注入到该属性中,然后将Baz传递给SomeUtil的构造函数,然后返回它刚刚创建的SomeUtil。

如果您不清楚这一点,我强烈建议您阅读a good book about Dependency Injection。学习识别依赖注入的模式和反模式需要花费一点时间和练习,但是一旦你这样做就会非常有益。

答案 4 :(得分:-1)

如果我正确理解了这个问题,那么解决方案非常简单。

完全按照您的方式声明BasePage对象,并添加abstract="true"属性:

<object id="BasePage" type="BasePage" abstract="true">
  <property name="FooProp">
    <object type="ConcreteInjectable"><property.... /></object>
  </property>
</object>

所有派生的实例都应该像这样配置:

<object type="somePage" parent="BasePage"/>

如果您不想单独配置每个实例,则应考虑使用MEF,这使得此任务更容易预先形成。