У нас есть конкретный одноэлементный сервис, который реализует Ninject.IInitializable
и 2 интерфейса. Проблема состоит в том, что сервисы Инициализируют-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, хотя атрибуты все еще остаются).
Ninject 3.0 теперь поддерживает несколько общих типов в вызове для привязки, то, что вы пытаетесь сделать, можно легко выполнить в одно цепочечное заявление.
kernel.Bind<IService1, IService2>()
.To<ConcreteService>()
.InSingletonScope();
Вы устанавливаете две разные привязки K => T и L => T. Запрашивающие экземпляры L вернут временные экземпляры T. Запрос K вернет одноэлементный экземпляр T.
В Ninject 2.0 область объектов определяется для каждого интерфейса службы, привязанного к обратному вызову области.
Когда у вас есть
Bind<IFoo>...InSingletonScope();
Bind<IBar>...InSingletonScope();
, вы создаете два разных прицела.
Вы говорите "Привязка к IFoo приведет к тому же объекту, который был возвращен. когда вызвали .Get ". а также "Привязка к 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>() );
в чтобы заставить несколько интерфейсов разрешаться в один и тот же экземпляр синглтона. Когда я вижу подобные ситуации, мне всегда приходится спрашивать, не слишком ли много делает ваш объект, если у вас есть синглтон с двумя обязанностями?
Обновление: совершенно уверен, что использование нескольких перегрузок Bind V3 решит эту проблему; См. этот вопрос / ответ
Хороший вопрос.
Если посмотреть на источник, бит инициализации происходит после каждого Activate
. Ваш Bind ... ToMethod
тоже считается одним. Стратегия применяется довольно единообразно - в отдельных случаях невозможно отказаться.
Ваш обходной путь - использовать явное OnActivation
в вашем Bind
, которое будет делать это условно (но для того, чтобы сделать это обычным способом, потребуется поддерживать Набор инициализированных объектов ( Я не смотрел, есть ли механизм, позволяющий прикрепить флаг к активированному объекту)), или сделать ваш идемпотентный вариант Initialize любыми наиболее удобными для вас средствами.
РЕДАКТИРОВАТЬ:
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
) инициализируется каждый раз.
РЕДАКТИРОВАТЬ 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>() );
}
}
Я думаю, что один из вариантов - вы создаете объект в модуле и связываете свой объект с каждым из интерфейсов.
BTW, постарайтесь не использовать специфичный для контейнеров код в вашем производственном коде. Если вам приходится это делать, используйте какие-нибудь помощники и изолируйте их в проекте модуля.
public class ServiceModule : NinjectModule
{
public override void Load()
{
ConcreteService svc = new ConcreteService();
Bind<IService1>().ToConstant(svc);
Bind<IService2>().ToConstant(svc);
....
}
}