Удалить контекстную привязку по умолчанию - PullRequest
19 голосов
/ 14 мая 2011

У меня есть интерфейс с несколькими различными конкретными реализациями. Я пытаюсь дать Ninject значение по умолчанию для использования и использовать другую реализацию, только если имя совпадает. Например, у меня есть следующие привязки.

Bind<ISomething>().To<DefaultSomething>()
Bind<ISomething>().To<OtherSomething>().Named("55abd8b8-097f-4e1c-8d32-95cc97910604");

Что бы я хотел, если бы раздел Named не совпадал, использовать реализацию DefaultSomething. Когда я передаю явно связанный guid, он работает нормально. Когда я передаю любое другое руководство, я получаю исключение "Нет доступных привязок".

Bind<ISomething>().To<OtherSomething>().Named("55abd8b8-097f-4e1c-8d32-95cc97910604");
Bind<ISomething>().To<DefaultSomething>()

Bind<ISomething>().To<DefaultSomething>()
Bind<ISomething>().To<OtherSomething>().When(ctx => ctx.Service != null && ctx.Service.Name == "55abd8b8-097f-4e1c-8d32-95cc97910604");

Я также пытался использовать .When для проверки привязки, и я пытался изменить порядок, как показано ниже, однако я никогда не смогу выполнить привязку, если я не передам Guid с явным именем.

Эта статья , кажется, указывает, что привязки по умолчанию работают, поэтому я, должно быть, делаю что-то не так. Есть предложения?


Редактировать: Вот полный пример, показывающий проблему, которую я пытаюсь решить. Требуемое поведение для kernel.Get<INumber>("Three").Write() для возврата "Unknown Number"

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ninject;

namespace NinjectTest
{
    interface INumber
    {
        string Write();
    }

    class UnknownNumber : INumber
    {
        public string Write()
        {
            return "Unknown Number";
        }
    }

    class One : INumber
    {
        public string Write()
        {
            return "1 = One";
        }
    }

    class Two : INumber
    {
        public string Write()
        {
            return "2 = Two";
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            StandardKernel kernel = new StandardKernel();
            kernel.Bind<INumber>().To<UnknownNumber>();
            kernel.Bind<INumber>().To<One>().Named("One");
            kernel.Bind<INumber>().To<Two>().Named("Two");

            Console.WriteLine(kernel.Get<INumber>("One").Write());
            Console.WriteLine(kernel.Get<INumber>("Two").Write());
            Console.WriteLine(kernel.Get<INumber>("Three").Write());

            Console.ReadLine();
        }
    }
}

Ответы [ 3 ]

22 голосов
/ 14 мая 2011

Вы совершенно не поняли именованные привязки:

Предоставление привязки имени НЕ является условием.Вы все равно получите их при запросе без ограничений.Добавление имени само по себе абсолютно ничего не меняет.

Запрос экземпляра с использованием имени добавляет ограничение:

должны быть возвращены только те привязки, чье имя совпадает с заданным

В вашем случае вы дали мне экземпляр, имя привязки которого "three".И вы ожидаете, что он вернет UnknownNumber, у которого даже нет имени.

Это может быть достигнуто путем либо

  1. передачи параметра и добавленияусловия для привязок, которые проверяют, соответствует ли параметр, или
  2. , передавая ограничение, которое соответствует имени или безымянному экземпляру, и объявляют неименованный один неявным.

Вариант 1:

public class CustomerIdParameter : Parameter
{
    public CustomerIdParameter(string id) : base("CustomerId", (object)null, false)
    {
        this.Id = id;
    }
    public string Id { get; private set; }
}

kernel.Bind<ISomething>().To<Default>();
kernel.Bind<ISomething>().To<Other>()
      .When(r => r.Parameters.OfType<CustomerIdParameter>()
                             .Single().Id == "SomeName");

kernel.Get<IWeapon>(new CustomerIdParameter("SomeName")).ShouldBeInstanceOf<Sword>();

Я оставляю за вами право написать методы расширения для упрощения определения и разрешения.

Вариант 2:

Bind<ISomething>().To<Default>().Binding.IsImplicit = true;
Bind<ISomething>().To<Other>().Named("SomeName")

public static T GetNamedOrDefault<T>(this IKernel kernel, string name)
{
    return kernel.Get<T>(m => m.Name == null || m.Name == name);
}

Но, честно говоря, я думаю, что то, что вы хотите сделать, не похоже на правильный дизайн:

  1. Держите ваш доступ к ядру до абсолютного минимума.Здесь вы используете ServiceLocator-подобный использование Ninject.
  2. Если для ожидаемого экземпляра нет привязки, я предпочел бы ожидать исключения, чем использовать экземпляр по умолчанию, потому чтоэто ошибка.
5 голосов
/ 14 мая 2011

Это вполне возможно сделать в Ninject, просто так не получается, как разрешение ведет себя по умолчанию.Расширение IKernel.Get<T> не запрашивает привязку «по умолчанию», оно запрашивает привязку any ;другими словами, он не применяет никаких ограничений.Если существует более одной совпадающей привязки, то он генерирует исключение для этого эффекта.

Попробуйте эти два метода расширения:

static class KernelExtensions
{
    public static T GetDefault<T>(this IKernel kernel)
    {
        return kernel.Get<T>(m => m.Name == null);
    }

    public static T GetNamedOrDefault<T>(this IKernel kernel, string name)
    {
        T namedResult = kernel.TryGet<T>(name);
        if (namedResult != null)
            return namedResult;
        return kernel.GetDefault<T>();
    }
}

Первый получает привязку «по умолчанию», т.е.Кого бы вы ни связали, у которого нет имени .Второй пытается получить именованную привязку, но если он этого не находит, он возвращается к значению по умолчанию.


Конечно, Remo также не ошибается;вам следует избегать использования Ninject или любого другого контейнера таким образом, если у вас нет для этого особых причин.Это шаблон локатора служб (анти), а не внедрение истинных зависимостей.Вы должны использовать синтаксис When для условных привязок, либо использовать сложные условия, либо просто декорировать классы, которые требуют специальных привязок, то есть:

Bind<IFoo>().To<SpecialFoo>().WhenInjectedInto<ClassThatNeedsSpecialFoo>();

или ...

Bind<IFoo>().To<SpecialFoo>().WhenMemberHas<SpecialAttribute>();

class InjectedClass
{
    public InjectedClass([Special]IFoo) { ... }
}

Это правильный способ обработки стандартных и условных привязок.Именованные привязки действительно полезны только тогда, когда вы пытаетесь реализовать фабричный шаблон и хотите обернуть контейнер IoC на своей собственной фабрике.Это нормально, но, пожалуйста, используйте его экономно, так как в конечном итоге вы выбрасываете многие / большинство преимуществ внедрения зависимостей таким образом.


В качестве альтернативы, вы можете реализовать собственное поведение активации и использовать его дляпереопределить значение по умолчанию в Ninject - все является модульным и попадает в коллекцию «Компоненты».Но это не для слабонервных, поэтому я не планирую включать здесь подробное руководство.

4 голосов
/ 24 июня 2013

Вы также можете просто добавить условие для привязки, чтобы оно не имело условия, например:

kernel.Bind<IObject>().To<Object1>().When(
           x => x.ParentContext != null && !x.ParentContext.Binding.IsConditional)
          .InRequestScope();

kernel.Bind<IObject>().To<Object2>().InRequestScope()
          .Named("WCFSession");

При выполнении стандартного Inject без указания имени будет использоваться первая привязка.При указании имени будет использоваться именованная привязка.Это не самое красивое решение, но оно работает.

...