Ninject默认上下文绑定

Tim*_*ple 19 c# ninject

我有一个具有几个不同具体实现的接口.我试图给Ninject一个默认使用,如果名称匹配,只使用其他实现.例如,我有以下绑定.

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

我想要的是,如果命名部分不匹配,使用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");
Run Code Online (Sandbox Code Playgroud)

我也尝试过使用.当检查绑定时,我尝试颠倒了下面的顺序但是我永远无法绑定,除非我传入明确命名的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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Rem*_*oor 22

你完全错过了命名绑定:

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

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

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

在您的情况下,您给了我一个绑定名称为的实例"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>();
Run Code Online (Sandbox Code Playgroud)

我留给你编写扩展方法,使定义和解决更容易.

选项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);
}
Run Code Online (Sandbox Code Playgroud)

但老实说,我认为你想做的事似乎不是一个合适的设计:

  1. 使您对内核的访问权限保持在最低限度.你在这里做的是与Ninject 类似ServiceLocator.
  2. 如果没有可用于预期实例的绑定,我宁愿期望异常而不是使用默认实例,因为这是一个错误.

  • 命名绑定不是条件?当他们说"命名绑定是最简单(也是最常见)的条件绑定形式"时,Ninject维基上的文档是错误的(https://github.com/ninject/ninject/wiki/Contextual-Binding)然后他们去了以我在我的示例中使用的方式使用它,除非没有名称匹配的默认回退. (3认同)

Aar*_*ght 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>();
    }
}
Run Code Online (Sandbox Code Playgroud)

第一个获得"默认"绑定 - 即您绑定的没有名称的绑定.第二个尝试获取命名绑定,但如果找不到,则恢复为默认值.


当然,雷莫也没错; 你应该避免以这种方式使用Ninject或任何其他容器,除非你有特别好的理由.这是服务定位器(反)模式,而不是真正的依赖注入.您应该使用When条件绑定的语法,使用复杂条件或仅修饰需要特殊绑定的类,即:

Bind<IFoo>().To<SpecialFoo>().WhenInjectedInto<ClassThatNeedsSpecialFoo>();
Run Code Online (Sandbox Code Playgroud)

要么...

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

class InjectedClass
{
    public InjectedClass([Special]IFoo) { ... }
}
Run Code Online (Sandbox Code Playgroud)

这是处理默认和条件绑定的正确方法.命名绑定实际上只在您尝试实现工厂模式并且希望将IoC容器包装在自定义工厂中时才有用.没关系,但是请谨慎使用它,因为你最终会以这种方式抛弃依赖注入的许多/大部分好处.


或者,您实际上可以实现自己的激活行为并使用它来覆盖Ninject中的默认行为 - 一切都是模块化的,并且被推入"组件"集合中.但这不适合胆小的人,所以我不打算在这里包含详细的教程.


Par*_*arm 5

您还可以简单地为您的绑定添加一个条件,使其没有条件,如下所示:

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

kernel.Bind<IObject>().To<Object2>().InRequestScope()
          .Named("WCFSession");
Run Code Online (Sandbox Code Playgroud)

在没有指定名称的情况下执行标准注入时,将使用第一个绑定。指定名称时,将使用命名绑定。这不是最漂亮的解决方案,但它有效。