如何使用约定根据注入的内容有条件地将接口绑定到类型?
How do I use conventions to conditionally bind an interface to a type depending on what it's injected into?
我有许多类型的构造函数采用 ICommand
参数,如下所示:
public AboutCommandMenuItem(ICommand command)
: base(command)
{
}
public OptionsCommandMenuItem(ICommand command)
: base(command)
{
}
我正在使用 Ninject 并且我的 ICommand
界面配置如下:
_kernel.Bind<ICommand>().To<AboutCommand>().WhenInjectedExactlyInto<AboutCommandMenuItem>();
_kernel.Bind<ICommand>().To<OptionsCommand>().WhenInjectedExactlyInto<OptionsCommandMenuItem>();
有没有一种方法可以设置约定,以便在 ICommand
接口注入 XxxxCommandMenuItem
时将其绑定到 XxxxCommand
,从而避免手动配置所有可能的方法类型接口可以注入?
我试过 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(?)));
其中 ?
是选择绑定的 class 的名称。
我是否坚持使用显式绑定?
先行者:
根据其他限制,调整设计以不共享 ICommand
接口可能会更好。为什么?将 OptionsCommand
注入 AboutCommandMenuItem
永远没有意义。但是 AboutCommandMenuItem
的 ctor 让它看起来好像没问题。
但是,我假设您仍想继续进行此操作。以下是您问题的几种可能解决方案(不影响您的设计选择):
- Named Bindings. You can use a convention with a 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>();
}
}
绑定生成器:
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);
}
}
命令和菜单项:
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) { }
}
我有许多类型的构造函数采用 ICommand
参数,如下所示:
public AboutCommandMenuItem(ICommand command)
: base(command)
{
}
public OptionsCommandMenuItem(ICommand command)
: base(command)
{
}
我正在使用 Ninject 并且我的 ICommand
界面配置如下:
_kernel.Bind<ICommand>().To<AboutCommand>().WhenInjectedExactlyInto<AboutCommandMenuItem>();
_kernel.Bind<ICommand>().To<OptionsCommand>().WhenInjectedExactlyInto<OptionsCommandMenuItem>();
有没有一种方法可以设置约定,以便在 ICommand
接口注入 XxxxCommandMenuItem
时将其绑定到 XxxxCommand
,从而避免手动配置所有可能的方法类型接口可以注入?
我试过 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(?)));
其中 ?
是选择绑定的 class 的名称。
我是否坚持使用显式绑定?
先行者:
根据其他限制,调整设计以不共享 ICommand
接口可能会更好。为什么?将 OptionsCommand
注入 AboutCommandMenuItem
永远没有意义。但是 AboutCommandMenuItem
的 ctor 让它看起来好像没问题。
但是,我假设您仍想继续进行此操作。以下是您问题的几种可能解决方案(不影响您的设计选择):
- Named Bindings. You can use a convention with a IBindingGenerator 创建绑定
- 您已经找到的具有
When
条件的方法。同样,将它与 IBindingGenerator- 备选方案 a) 条件检查名称或类型。匹配类型计算为
When
表达式执行的一部分 - 备选方案 b) 绑定生成器计算匹配类型,而
When
表达式仅执行比较。
- 备选方案 a) 条件检查名称或类型。匹配类型计算为
最后一个选项/备选方案的示例实现:
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>();
}
}
绑定生成器:
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);
}
}
命令和菜单项:
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) { }
}