На днях столкнулся с такой проблемой - как использовать 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 вам вообще не нужен - все можно получить с помощью статических хелперов.