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

Контекстная привязка по умолчанию Ninject

У меня есть интерфейс с несколькими различными конкретными реализациями. Я пытаюсь дать 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");

Я также пытался использовать. Если проверить привязку, и я попытался изменить порядок, как показано ниже, я никогда не смогу связать, если не передать в 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();
        }
    }
}
4b9b3361

Ответ 1

Вы полностью пропустили именованные привязки:

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

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

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

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

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

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

Вариант 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);
}

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

  • Держите доступ к ядру до абсолютного минимума. То, что вы здесь делаете, - это использование службы > службы > .
  • Если привязка недоступна для ожидаемого экземпляра, я предпочел бы ожидать исключения, чем использование экземпляра по умолчанию, потому что это ошибка.

Ответ 2

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

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

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 или любого другого контейнера таким образом, если у вас нет особо веских оснований. Это шаблон локатора службы (anti-), а не истинная инъекция зависимостей. Вы должны использовать синтаксис When для условных привязок, либо используя сложные условия, либо просто украшая классы, которые нуждаются в специальных привязках, то есть:

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

или...

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

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

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


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

Ответ 3

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

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

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

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