Как автоматически внедрить все дочерние объекты - PullRequest
2 голосов
/ 30 июня 2010

Рассмотрим этот абстрактный класс

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

У меня есть приложение, которое я хочу улучшить и одновременно сделать рефакторинг для простоты.У меня есть более 100 классов, которые вызывают одни и те же вещи (например, Class Injectable), и я думаю, что это поведение можно абстрагировать и установить в базовый класс, и каждый будет наследовать от этого базового класса, чтобы удалить код копирования / вставки,

Однако я хочу избежать копирования / вставки кода в конфигурационный файл Spring, определив объект Injectable, а затем определив дочерний объект foreach и каждый класс, который наследуется от Foo.

Я ищу способ установить свойства абстрактного класса, а затем все дочерние элементы, чтобы автоматически получать их через конфигурацию.Я хочу избежать создания абстрактного класса следующим образом:

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

спасибо за любые предложения

РЕДАКТИРОВАТЬ: чтобы сделать вещи немного сложнее, дочерние классы являются страницами ASP.NET, поэтомуя имею ограниченный контроль над тем, как они генерируются.

В настоящее время я использую вышеупомянутый код, где абстрактный класс делает ссылку на созданный объект DI с идентификатором «Injectable».Я хотел бы избежать летающих строк

ОБНОВЛЕНИЕ (с решением)

Рассмотрим: Классы

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 из моего кода.

2-е ОБНОВЛЕНИЕ И РЕШЕНИЕ благодаря tobsen и Эрих Эйхингер решение было найдено: во-первых, это изначально не поддерживается, но решение не очень уродливо и не нарушает нормы паттернов DI

Теперь,вся необходимая конфигурация пружины приведена выше (в обновлении). Решение Эриха таково: 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 ]

3 голосов
/ 08 июля 2010

извините, у меня есть только ограниченное время atm, плз ping мне, если описание ниже должно быть слишком коротким / абстрактным

Во-первых, такой функциональности пока нет. Но с помощью нескольких строк кода вы можете сделать это самостоятельно.

В целом ниже я буду продолжать идею отображения определенного класса на определение объекта, которое будет использоваться для настройки экземпляра. Что-то вроде

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. Внедрение зависимостей в пользовательские модули HTTP .

НТН, Эрих

2 голосов
/ 06 июля 2010

Вы можете использовать автопроводку (см. 5.3.6. Соавторы автопроводки в документации по Spring.net) для автоматической установки этого свойства Injectable через пружину, когда класс ничего не знает о пружине.

Вы написали: «Я хочу избежать создания абстрактного класса подобным образом», и это мудрый выбор, поскольку вам не следует разговаривать с инверсией контейнера управления.

Интересно, кто это?ответственность за создание экземпляров этих классов, производных от Foo?Я думаю, вы должны позволить Spring создавать объекты из тех классов страниц Asp.Net, которые получены из FooКажется, Spring имеет обширную поддержку страниц Asp.net (Spring.Web.Support.PageHandlerFactory, Spring.Web.Services.WebServiceHandlerFactory и т. Д. В Spring.Web) - я бы опубликовал пример конфигурации, подходящий для ваших нужд, но я не сделалиспользовал ASP.Net еще.Вместо этого вот несколько ссылок:

Вы также должны взглянуть на примеры веб-сайтов Spring, которые являются частью распределения исходного кода.

== BeginEdit ==

В порядкеЧтобы ответить на это, мне нужно знать, какова логика принятия решения для создания экземпляров детей.т.е. когда / где конкретный дочерний объект запрашивается кем?Кто выбирает конкретный тип объекта для создания?Если у вас был контроль над созданием конкретного дочернего класса, вы могли бы создать экземпляр конкретного типа и сказать Spring, чтобы впоследствии установить свойства:

void ConfigureObject (target target) : Внедряет зависимости в предоставленный целевой экземпляр.Имя определения абстрактного объекта - это System.Type.FullName целевого экземпляра.Этот метод обычно используется, когда объекты создаются вне элемента управления разработчика, например, когда ASP.NET создает экземпляры веб-элементов управления и когда приложение WinForms создает UserControls.

void ConfigureObject (цель объекта, имя строки) : предлагает те же функциональные возможности, что и ранее перечисленный метод Configure, но использует определение именованного объекта вместо использования полного имени типа.

Это сохранит POCO ваших объектов, и у вас будет только одна точкасоздания объекта с зависимостью от весны.

Если логику того, какой тип создать экземпляр, можно разрешить с помощью регулярного выражения для запроса страницы, Spring также найдет решение для этого: 22.4.3.Внедрение зависимостей в обработчики HTTP и фабрики обработчиков .Однако вы должны сообщить Spring, какое определение объекта следует создать, и, похоже, существует автоматизм, который может быть полезен в вашем случае (в зависимости от того, какова логика создания ваших дочерних классов) (также 22.4.3 из документации 1.3 Spring):

SpringHandlerFactory в Spring использует класс .NET System.Web.UI.SimpleHandlerFactory для создания экземпляров обработчиков, а настраивает каждый экземпляр с использованием определения объекта, имя которого соответствует запросуИмя файла URL .Определение абстрактного объекта DemoHandler.ashx является примером такого подхода.Вы также можете настроить стандартные классы, которые реализуют интерфейс IHttpHandler, как показано в примере выше для класса MyCustomHttpHandler.

== EndEdit ==

== Begin2ndEdit ==

Похоже, что у Эриха Айхингера (одного из разработчиков spring.net) однажды была точно такая же проблема, как у вас .Похоже, в конце концов ему пришлось говорить с контейнером точно так же, как вы.Как пишет Эрих в ветке форума, он не был слишком счастлив иметь прямую зависимость от контейнера IoC.Возможно, его предложенное решение было интегрировано в Spring.Я постараюсь выяснить.

== End2ndInit ==

0 голосов
/ 08 июля 2010

Если я правильно понял вопрос, решение очень простое.

Объявите объект 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, что значительно упрощает выполнение этой задачи.

0 голосов
/ 03 июля 2010

Большинство структур внедрения зависимостей допускают привязку инъекций по "соглашению". Например, вы можете поместить атрибут в свойство, чтобы пометить его для внедрения, и когда структура 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, но это общее направление, в котором я хотел бы взглянуть.

Обновление

Когда я говорил о связывании по «соглашению», я имел в виду автоматическую разводку. В ответе Тобсена есть несколько хороших упоминаний об автопроводке весной. Похоже, это сложнее, чем в 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, который он только что создал.

Если вам это не совсем понятно, я настоятельно рекомендую вам прочитать хорошую книгу о внедрении зависимости . Требуется немного времени и практики, чтобы научиться распознавать шаблоны и анти-шаблоны внедрения зависимости, но это очень полезно, когда вы это сделаете.

0 голосов
/ 03 июля 2010

Я думаю, что вы предлагаете класс, который получает список свойств из файла конфигурации. Если это правда, вам следует заглянуть в Code Generators. Вы можете создать простой генератор кода, который читает файл конфигурации и создает свойства в данных дочерних классах. Code Smith должно быть хорошим началом.

...