如何使用约定将接口有条件地绑定到类型,具体取决于它注入的内容?

Mat*_*don 3 c# ninject

我有许多类型,构造函数采用ICommand参数,如下所示:

public AboutCommandMenuItem(ICommand command)
    : base(command)
{
}
Run Code Online (Sandbox Code Playgroud)
public OptionsCommandMenuItem(ICommand command) 
    : base(command)
{
}
Run Code Online (Sandbox Code Playgroud)

我正在使用Ninject,我的ICommand接口配置如下:

_kernel.Bind<ICommand>().To<AboutCommand>().WhenInjectedExactlyInto<AboutCommandMenuItem>();
_kernel.Bind<ICommand>().To<OptionsCommand>().WhenInjectedExactlyInto<OptionsCommandMenuItem>();
Run Code Online (Sandbox Code Playgroud)

有没有办法设置一个约定,以便我可以将ICommand接口绑定到XxxxCommand它注入的时间XxxxCommandMenuItem,并避免必须手动配置接口可以注入的每种可能的类型?

我试过BindToSelection但是选择器表达式没有捕获我注入的类型,而且BindToRegex似乎只是一个字符串类型的选择器.

这是我能得到的最接近的:

_kernel.Bind(t => t.FromThisAssembly()
    .SelectAllClasses()
    .InNamespaceOf<ICommand>()
    .EndingWith("Command")
    .Where(type => type.GetInterfaces().Contains(typeof(ICommand)))
    .BindAllInterfaces()
    .Configure(binding => binding
        .When(request => request.Service == typeof(ICommand) 
                      && request.Target.Member.DeclaringType.Name.StartsWith(?)));
Run Code Online (Sandbox Code Playgroud)

?选择用于绑定的类的名称在哪里?

我是否坚持使用显式绑定?

Bat*_*nit 5

前体:根据其他约束条件,最好使设计适应不共享ICommand接口.为什么?它将永远是有意义的注入OptionsCommand进入AboutCommandMenuItem.但是,AboutCommandMenuItem它使它看起来好像没问题.

但是,我会假设你仍然希望继续这样做.以下是您的问题的几种可能的解决方案(不影响您的设计选择):

  • 命名绑定.您可以使用IBindingGenerator的约定来创建绑定
  • 具有When您已经找到的条件的方法.再次,将它与IBindingGenerator结合使用
    • 备选方案a)条件检查名称或类型.匹配类型作为When表达式执行的一部分计算
    • 备选方案b)绑定生成器计算匹配类型,When表达式仅执行比较.

最后一个选项/替代方案的范例实施:

Codez

(有趣的部分首先)

测试(演示ItWorxxTm)

using FluentAssertions;
using Ninject;
using Ninject.Extensions.Conventions;
using Xunit;

public class MenuItemCommandConventionTest
{
    [Fact]
    public void Test()
    {
        var kernel = new StandardKernel();
        kernel.Bind(x => x
            .FromThisAssembly()
            .IncludingNonePublicTypes()
            .SelectAllClasses()
            .InheritedFrom<ICommand>()
            .BindWith<CommandBindingGenerator>());

        kernel.Get<AboutMenuItem>()
              .Command.Should().BeOfType<AboutCommand>();
        kernel.Get<OptionsMenuItem>()
              .Command.Should().BeOfType<OptionsCommand>();
    }
}
Run Code Online (Sandbox Code Playgroud)

绑定生成器:

using Ninject.Extensions.Conventions.BindingGenerators;
using Ninject.Syntax;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;

public class CommandBindingGenerator : IBindingGenerator
{
    private const string CommandSuffix = "Command";
    private const string MenuItemTypeNamePattern = "{0}MenuItem";

    public IEnumerable<IBindingWhenInNamedWithOrOnSyntax<object>> CreateBindings(
        Type type, IBindingRoot bindingRoot)
    {
        string commandName = GetCommandName(type);

        Type menuItem = FindMatchingMenuItem(type.Assembly, commandName);

        var binding = bindingRoot.Bind(typeof(ICommand)).To(type);

        // this is a slight hack due to the return type limitation
        // but it works as longs as you dont do Configure(x => .When..)
        binding.WhenInjectedInto(menuItem); 
        yield return binding;
    }

    private static Type FindMatchingMenuItem(
        Assembly assembly, string commandName)
    {
        string expectedMenuItemTypeName = string.Format(
            CultureInfo.InvariantCulture, 
            MenuItemTypeNamePattern,
            commandName);

        Type menuItemType = assembly.GetTypes()
            .SingleOrDefault(x => x.Name == expectedMenuItemTypeName);

        if (menuItemType == null)
        {
            string message = string.Format(
                CultureInfo.InvariantCulture,
                "There's no type named '{0}' in assembly {1}",
                expectedMenuItemTypeName,
                assembly.FullName);
            throw new InvalidOperationException(message);
        }

        return menuItemType;
    }

    private static string GetCommandName(Type type)
    {
        if (!type.Name.EndsWith(CommandSuffix))
        {
            string message = string.Format(
                CultureInfo.InvariantCulture,
                "the command '{0}' does not end with '{1}'",
                type.FullName,
                CommandSuffix);
            throw new ArgumentException(message);
        }

        return type.Name.Substring(
            0,
            type.Name.Length - CommandSuffix.Length);
    }
}
Run Code Online (Sandbox Code Playgroud)

命令和菜单项:

public interface ICommand
{ 
}

class AboutCommand : ICommand
{
}

internal class OptionsCommand : ICommand
{
}

public abstract class MenuItem
{
    private readonly ICommand command;

    protected MenuItem(ICommand command)
    {
        this.command = command;
    }

    public ICommand Command
    {
        get { return this.command; }
    }
}

public class OptionsMenuItem : MenuItem
{
    public OptionsMenuItem(ICommand command)
        : base(command) { }
}

public class AboutMenuItem : MenuItem
{
    public AboutMenuItem(ICommand command)
        : base(command) { }
}
Run Code Online (Sandbox Code Playgroud)

  • 哇哇 这就是我在凌晨4点编写代码所得到的...我建议你看一下[代码的完整上下文](http://codereview.stackexchange.com/q/98979/23788)并在那里得到更多代表根据你的第一段回答.无论如何,这是一个非常有教育意义的答案,谢谢! (2认同)