Запретить вызов Ninject Инициализировать несколько раз при привязке к нескольким интерфейсам - PullRequest
20 голосов
/ 15 июня 2010

У нас есть конкретный одноэлементный сервис, который реализует 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 initialized-member к ConcreteService и инициализировать только тогда, когда оно ложно, но это похоже на хак. И это потребовало бы повторения одной и той же логики в каждом сервисе, который реализует два или более интерфейса.


Спасибо за все ответы! Я чему-то научился у всех! (Мне трудно решить, какая из них правильная).

Мы закончили тем, что создали интерфейс IActivable и расширили ядро ​​ninject (оно также убрало приятные зависимости уровня кода до ninject, хотя атрибуты все еще остаются).

Ответы [ 3 ]

29 голосов
/ 15 июня 2010

Ninject 3

Ninject 3.0 теперь поддерживает несколько универсальных типов в вызове привязки, и то, что вы пытаетесь сделать, может быть легко выполнено с помощью одной цепочки операторов.

kernel.Bind<IService1, IService2>()
      .To<ConcreteService>()
      .InSingletonScope();

Ninject2

Вы устанавливаете две разные привязки 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>() );

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

2 голосов
/ 15 июня 2010

Обновление: уверен, что использование множественных перегрузок Bind V3 решит эту проблему; См. это Q / A


Хороший вопрос.

При просмотре источника бит инициализации происходит после каждого Activate. Ваш Bind...ToMethod тоже считается одним. Стратегия применяется довольно единообразно - в определенных случаях нет возможности отказаться.

Ваши варианты обходного пути - использовать явный OnActivation в вашем Bind, который будет делать это условно (но для того, чтобы сделать это в общем случае, потребуется поддержка набора инициализированных объектов (не смотрел, чтобы увидеть, есть ли механизм для того, чтобы спрятать флаг против активированного объекта)), или сделать ваш инициализированный идемпотент любыми способами, которые наиболее чисты для вас.

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) каждый раз инициализируется.

РЕДАКТИРОВАТЬ 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>() );
    }
}
0 голосов
/ 15 июня 2010

Я думаю, что один из вариантов заключается в том, что вы создаете свой собственный объект в модуле и связываете свой объект с каждым из интерфейсов.

Кстати, постарайтесь не использовать какой-либо конкретный контейнерный код в своем производственном коде,Если вам нужно сделать это, используйте некоторый помощник и изолируйте их в проекте модуля.

public class ServiceModule : NinjectModule
{

    public override void Load()
    { 
         ConcreteService svc = new ConcreteService();
         Bind<IService1>().ToConstant(svc);
         Bind<IService2>().ToConstant(svc);
         ....
     }
}
...