Подтвердить что ты не робот

Запретить 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, все атрибуты все еще остаются).

4b9b3361

Ответ 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);
         ....
     }
}