我有一个带有几个不同具体实现的接口。我试图给Ninject一个默认使用,如果名称匹配,只使用其他实现。例如,我有以下绑定。
Bind<ISomething>().To<DefaultSomething>()
Bind<ISomething>().To<OtherSomething>().Named("55abd8b8-097f-4e1c-8d32-95cc97910604");
如果命名部分不匹配,我想要的是使用DefaultSomething实现。当我传入明确绑定的guid时,它工作正常。当我传入任何其他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。
This article似乎表明默认绑定有效,所以我一定做错了。有什么建议吗?
编辑:这是一个完整的示例,显示了我要解决的问题。所需行为是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();
}
}
}
答案 0 :(得分:22)
你完全错误地理解了命名绑定:
赋予绑定名称不是条件。在没有约束的情况下请求它们时,您仍将获得所有这些。添加名称本身就没有任何改变。
使用名称请求实例会添加约束:
只返回名称与给定名称匹配的绑定
在您的情况下,您给了我一个绑定名称为"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);
}
但老实说,我认为你想做的事情似乎并不合适:
答案 1 :(得分:5)
很有可能在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>();
}
}
第一个获得“默认”绑定 - 即你绑定的那个 no name 。第二个尝试获取命名绑定,但如果找不到,则恢复为默认值。
当然,雷莫也没错;你应该避免以这种方式使用Ninject或任何其他容器,除非你有特别好的理由。这是服务定位器(反)模式,而不是真正的依赖注入。你应使用When
语法进行条件绑定,要么使用复杂条件,要么只修饰需要特殊绑定的类,即:
Bind<IFoo>().To<SpecialFoo>().WhenInjectedInto<ClassThatNeedsSpecialFoo>();
...或
Bind<IFoo>().To<SpecialFoo>().WhenMemberHas<SpecialAttribute>();
class InjectedClass
{
public InjectedClass([Special]IFoo) { ... }
}
这是处理默认和条件绑定的正确方式。命名绑定实际上只在您尝试实现工厂模式并且希望将IoC容器包装在自定义工厂中时才有用。没关系,但是请谨慎使用它,因为你最终会抛弃依赖注入的许多/大多数好处。
或者,您实际上可以实现自己的激活行为并使用它来覆盖Ninject中的默认行为 - 一切都是模块化的,并且被推入“组件”集合中。但这不适合胆小的人,所以我不打算在这里提供详细的教程。
答案 2 :(得分:4)
您也可以简单地为绑定添加条件,使其没有条件,如下所示:
kernel.Bind<IObject>().To<Object1>().When(
x => x.ParentContext != null && !x.ParentContext.Binding.IsConditional)
.InRequestScope();
kernel.Bind<IObject>().To<Object2>().InRequestScope()
.Named("WCFSession");
在执行未指定Name的标准Inject时,将使用第一个绑定。指定名称时,将使用指定的绑定。这不是最漂亮的解决方案,但它确实有效。