воскресенье, 15 сентября 2013 г.

Использование Ninject в Windows Forms

На днях столкнулся с такой проблемой - как использовать Ninject совместно с дизайнером Windows Forms. Понятное дело, создать саму форму проблем нет. Для этого достаточно заменить строку new Form1() на kernel.Get<Form1>(). В частности, для главного окна программы, мы просто корректируем класс Program, заменяя строку Application.Run( new Form1() ); на:
var kernel = new StandardKernel( new MyInjectModule() );
Application.Run( kernel.Get<Form1>() );
Но что же делать с пользовательскими контролями и компонентами, размещенными в дизайнере? Ведь код для их создания генерируется автоматически. Тут нас выручит возможность произвести инъекцию в уже готовый экземпляр с помощью метода Inject, определенного в интерфейсе IKernel и специальный атрибут InjectAttribute, позволяющий делать инъекции в свойства и методы. Итак, предположим, мы создали наше окно через Ninject, указанным выше способом. В конструкторе нашего окна вызывается сгенерированный студией метод InitializeComponent, который отрабатывает код дизайнера, рекурсивно создавая контроли и компоненты, расположенные в окне. Далее, наша задача рекурсивно обойти все эти элементы, вызывая для каждого метод Inject. В общем-то, ничего сложного в этом нет. А, что бы было еще проще, я написал маленький класс-хелпер. Выглядит он следующим образом:
public static class NinjectHelper
{
 public static void InjectToComponent( this IKernel kernel, Component parent )
 {
  if( parent is Control )
   foreach( Control c in ((Control)parent).Controls )
   {
    kernel.Inject( c );
    kernel.InjectToComponent( c );
   }
  var cmpInfo = parent.GetType().GetField( "components", BindingFlags.NonPublic | BindingFlags.Instance );
  if( cmpInfo != null )
  {
   var cmp = cmpInfo.GetValue( parent ) as IContainer;
   if( cmp != null )
    foreach( Component c in cmp.Components )
    {
     kernel.Inject( c );
     kernel.InjectToComponent( c );
    }
  }
 }
}
Использовать его очень просто - для этого надо вызвать метод InjectToComponent сразу после метода InitializeComponent:
public Form1( IKernel kernel )
{
 InitializeComponent();
 kernel.InjectToComponent( this );
}
После этого мы можем получить инъекции в любом нашем подэлементе (контроле или компоненте) через атрибут InjectAttribute следующим образом:
[Inject]
public int A
{
 get { return a; }
 set
 {
  a = value;
  //do some things
 }
}
Или, аналогично, через метод:
[Inject]
public void SetA( int a )
{
 //do some things
}
P. S. Я уверен, что найдутся люди, которые скажут "Вай! Зачем такие сложности? Не проще положить kernel в статический класс и дергать его оттуда из любого места?". Не смотря, на очевидность ответа на этот вопрос, я все же его озвучу. Если вы пишете универсальные блоки кода, предполагая их использование из разных приложений, то вы не можете знать, где и как создается ядро kernel - ваш статический класс вполне может оказаться непроинициализированным. Если же вы пишете небольшое приложение, не предполагающее особой гибкости, то Ninject вам вообще не нужен - все можно получить с помощью статических хелперов.

Комментариев нет:

Отправить комментарий