Запретить Ninject вызывать Инициализировать несколько раз при привязке к нескольким интерфейсам
У нас есть конкретная однопользовательская услуга, которая реализует Ninject.IInitializable
и 2 интерфейса. Проблема в том, что службы Initialize-methdod вызывают 2 раза, когда требуется только одно. Мы используем .NET 3.5 и Ninject 2.0.0.0.
Есть ли шаблон в Ninject, чтобы это не происходило. Ни один из интерфейсов не реализует Ninject.IInitializable
. класс службы:
public class ConcreteService : IService1, IService2, Ninject.IInitializable
{
public void Initialize()
{
// This is called twice!
}
}
И модуль выглядит следующим образом:
public class ServiceModule : NinjectModule
{
public override void Load()
{
this.Singleton<Iservice1, Iservice2, ConcreteService>();
}
}
где Singleton - это метод расширения, определенный следующим образом:
public static void Singleton<K, T>(this NinjectModule module) where T : K
{
module.Bind<K>().To<T>().InSingletonScope();
}
public static void Singleton<K, L, T>(this NinjectModule module)
where T : K, L
{
Singleton<K, T>(module);
module.Bind<L>().ToMethod(n => n.Kernel.Get<T>());
}
Конечно, мы могли бы добавить инициализированный элемент bool в ConcreteService и инициализировать только тогда, когда он является ложным, но, похоже, он немного взломан. И это потребует повторения одной и той же логики в каждой службе, которая реализует два или более интерфейсов.
Спасибо за ответы! Я узнал что-то от всех! (Мне сложно определить, какая из них правильная).
Мы закончили создание интерфейса IActivable и расширение ядра ninject (он также удалил хорошо зависимые зависимости уровня кода до ninject, все атрибуты все еще остаются).
Ответы
Ответ 1
Ninject 3
Ninject 3.0 теперь поддерживает несколько общих типов в вызове bind, то, что вы пытаетесь сделать, может быть легко реализовано в одном закодированном заявлении.
kernel.Bind<IService1, IService2>()
.To<ConcreteService>()
.InSingletonScope();
Ninject 2
Вы настраиваете два разных привязки K = > T и L = > T. Запрос экземпляров L будет возвращать временные экземпляры T. Запрос K вернет один экземпляр T.
В Ninject 2.0 область объектов - это интерфейс службы, связанный с обратным вызовом области.
Если у вас
Bind<IFoo>...InSingletonScope();
Bind<IBar>...InSingletonScope();
вы создаете две разные области.
Вы говорите
"Привязка к IFoo будет разрешена к тому же возвращенному объекту
когда был вызван.
а также
"Связывание с IBar будет разрешено к тому же возвращенному объекту
когда был вызван .Get.
вы можете связать привязки вместе, но вам нужно будет удалить IInitializable, поскольку это приведет к дублированию инициализации при активации экземпляра:
kernel.Bind<IBoo>()
.To<Foo>()
.InSingletonScope();
.OnActivation(instance=>instance.Initialize());
kernel.Bind<IBaz>()
.ToMethod( ctx => (IBaz) ctx.Kernel.Get<IBoo>() );
или
kernel.Bind<Foo>().ToSelf().InSingletonScope()
.OnActivation(instance=>instance.Initialize());
kernel.Bind<IBaz>().ToMethod( ctx => ctx.Kernel.Get<Foo>() );
kernel.Bind<IBoo>().ToMethod( ctx => ctx.Kernel.Get<Foo>() );
чтобы получить несколько интерфейсов для разрешения одного и того же экземпляра singleton. Когда я вижу подобные ситуации, я всегда должен спрашивать, слишком ли слишком много, если у вас есть синглтон с двумя обязанностями?
Ответ 2
Обновление: Довольно уверенно, используя V3 multiple Bind overloads будет решать это; См. этот Q/A
Хороший вопрос.
От взгляда на источник бит инициализации происходит после каждого Activate
. Ваш Bind...ToMethod
также считается одним из них. Стратегия довольно единообразно применяется - в отдельных случаях не существует возможности отказаться.
Ваши варианты обхода - использовать явный OnActivation
в вашем Bind
, который сделает это условно (но для этого в общем случае потребуется поддерживать набор инициализированных объектов (havent посмотрел, есть ли механизм, чтобы спрятать флаг против активированного объекта)), или сделать ваш Инициализировать идемпотент с помощью любых средств, наиболее чистых для вас.
EDIT:
internal interface IService1
{
}
internal interface IService2
{
}
public class ConcreteService : IService1, IService2, Ninject.IInitializable
{
public int CallCount { get; private set; }
public void Initialize()
{
++CallCount;
}
}
public class ServiceModule : NinjectModule
{
public override void Load()
{
this.Singleton<IService1, IService2, ConcreteService>();
}
}
Учитывая следующие помощники:
static class Helpers
{
public static void Singleton<K, T>( this NinjectModule module ) where T : K
{
module.Bind<K>().To<T>().InSingletonScope();
}
public static void Singleton<K, L, T>( this NinjectModule module )
where T : K, L
{
Singleton<T, T>( module );
module.Bind<K>().ToMethod( n => n.Kernel.Get<T>() );
module.Bind<L>().ToMethod( n => n.Kernel.Get<T>() );
}
}
@Ian Davis et al. Проблема в том, что:
class Problem
{
[Fact]
static void x()
{
var kernel = new StandardKernel( new ServiceModule() );
var v1 = kernel.Get<IService1>();
var v2 = kernel.Get<IService2>();
var service = kernel.Get<ConcreteService>();
Console.WriteLine( service.CallCount ); // 3
Assert.AreEqual( 1, service.CallCount ); // FAILS
}
}
Поскольку каждая активация (за Bind
) инициализируется каждый раз.
EDIT 2: То же самое, если вы используете следующую чуть более урезанную версию:
static class Helpers
{
public static void Singleton<K, L, T>( this NinjectModule module )
where T : K, L
{
module.Bind<T>().ToSelf().InSingletonScope();
module.Bind<K>().ToMethod( n => n.Kernel.Get<T>() );
module.Bind<L>().ToMethod( n => n.Kernel.Get<T>() );
}
}
Ответ 3
Я думаю, что один из вариантов: вы создаете свой объект в модуле и привязываете свой объект к каждому из интерфейсов.
BTW, постарайтесь не использовать какой-либо конкретный код контейнера в вашем производственном коде. Если вам нужно это сделать, используйте вспомогательный помощник и изолируйте их в проекте модуля.
public class ServiceModule : NinjectModule
{
public override void Load()
{
ConcreteService svc = new ConcreteService();
Bind<IService1>().ToConstant(svc);
Bind<IService2>().ToConstant(svc);
....
}
}