Ninject默认上下文绑定

时间:2011-05-13 20:36:02

标签: c# ninject

我有一个带有几个不同具体实现的接口。我试图给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();
        }
    }
}

3 个答案:

答案 0 :(得分:22)

你完全错误地理解了命名绑定:

赋予绑定名称不是条件。在没有约束的情况下请求它们时,您仍将获得所有这些。添加名称本身就没有任何改变。

使用名称请求实例会添加约束:

  

只返回名称与给定名称匹配的绑定

在您的情况下,您给了我一个绑定名称为"three"的实例。而且你希望它返回UnknownNumber,它甚至没有名字。

这可以通过

来实现
  1. 传递参​​数并向检查参数是否匹配的绑定添加条件,
  2. 传递一个符合名称或未命名实例的约束,并声明隐含的未命名实例。
  3. 选项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. 将您对内核的访问权限保持在最低限度。你在这里做的是Ninject的类似ServiceLocator的用法。
    2. 如果没有可用于预期实例的绑定,我宁愿期望一个例外而不是使用默认实例,因为这是一个错误。

答案 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时,将使用第一个绑定。指定名称时,将使用指定的绑定。这不是最漂亮的解决方案,但它确实有效。