Как автоматически внедрить все дочерние объекты

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

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

У меня есть приложение, которое я хочу улучшить и одновременно реорганизовать для простоты. У меня есть более 100 классов, которые вызывают одни и те же вещи (например, класс 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();
   }
}

весна кфг...

<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 внутри моего кода.

Второе ОБНОВЛЕНИЕ И РЕШЕНИЕ благодаря tobsen и Erich Eichinger решение было найдено: во-первых, это не поддерживается изначально, но решение не очень уродливое и не нарушает нормы шаблона 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);
   }
}

и он работает как шарм, не требуя каких-либо дополнительных модулей и т. д.

К счастью, это работает и для пользовательских элементов управления (хотя и в другом событии жизненного цикла, поскольку 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);
   }
}

Спасибо за просмотр!


person Jaguar    schedule 30.06.2010    source источник


Ответы (5)


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

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

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

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.

хт, Эрих

person Erich Eichinger    schedule 08.07.2010

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

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

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

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

==Начать редактирование==

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

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

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

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

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

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

==Конец редактирования==

==Начало второго редактирования==

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

==End2ndInit==

person tobsen    schedule 05.07.2010
comment
спасибо, но я много раз читал справочный документ, и я до сих пор не могу понять, как НЕ объявлять дочерние элементы в конфигурации, а вместо этого объявлять только абстрактный класс - person Jaguar; 06.07.2010
comment
проверьте мое обновление, дети - это страницы ASP.NET, поэтому я не могу контролировать их создание - person Jaguar; 07.07.2010
comment
Теперь я понимаю вашу проблему и попытался найти хороший ответ. В настоящее время я изучаю это и отредактировал пост во второй раз. - person tobsen; 08.07.2010
comment
Конечно ;) весело провести время с весной. - person tobsen; 09.07.2010

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

person decyclone    schedule 03.07.2010
comment
то, что я предлагаю, для некоторых функций Spring.NET (если они существуют), эта конфигурация «знает» только абстрактный класс и оттуда признает существование дочерних элементов и вводит каждому дочернему элементу настроенные свойства - person Jaguar; 04.07.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, но это общее направление, в котором я буду искать.

Обновлять

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

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

person StriplingWarrior    schedule 03.07.2010
comment
это требует, чтобы я настроил каждого дочернего элемента этого абстрактного класса. Мы смотрим на 100++ классов, и в конце концов результатом будет копирование/вставка кода в файле конфигурации, а не в самом коде. - person Jaguar; 04.07.2010
comment
Это действительно не должно требовать явной настройки каждого дочернего класса. Смотрите мое обновление. Тобсен на правильном пути и знает о Spring больше, чем я, так что обязательно ответьте на его вопросы. - person StriplingWarrior; 06.07.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, который значительно упрощает выполнение этой задачи.

person HuBeZa    schedule 08.07.2010