Контекстная привязка по умолчанию 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();
}
}
}
Ответы
Ответ 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 без указанного имени будет использоваться первая привязка. При указании имени будет использоваться именованное связывание. Это не самое красивое решение, но оно работает.