Ninject 基于约定的绑定将在运行时解析

本文关键字:绑定 运行时 于约定 约定 Ninject | 更新日期: 2023-09-27 18:33:07

我正在使用命令处理程序模式并与ninject.extensions.TConvention绑定,当我的实际IQueryHandler<,>接口实现与单个具体类型匹配时,它运行良好。 这是我正在使用的:

kernel.Bind(x => x
    .FromThisAssembly()
    .SelectAllClasses()
    .InheritedFrom(typeof(IQueryHandler<,>))
    .BindSingleInterface()
    .Configure(b => b.WhenInjectedInto(typeof(ValidationHandlerDecorator<,>)).InRequestScope()));
kernel.Bind(typeof(IQueryHandler<,>)).To(typeof(PerformanceHandlerDecorator<,>)).InRequestScope();

但是我遇到了一个场景,我需要根据自定义路由值在运行时覆盖默认的具体类型。 以下内容工作没有问题:

    kernel.Bind<IQueryHandler<query1, result1>>().ToMethod(
    context => HttpContext.Current.Request.RequestContext.RouteData.Values["type"].ToString().ToLower() == "api"
        ? (IQueryHandler<query1, result1>)new apiHandler()
        : (IQueryHandler<query1, result1>)new defaultHandler()
)

上面的问题是我需要为我的每个 IQueryHandler<,> 泛型类型编写此代码。 此外,对于我想全局应用的每个装饰器(如顶部示例(,我必须修改每个绑定并添加它,使代码加倍或三倍。

我希望完成的是使用如下所示的内容。 我已经实现了一个类/接口来返回自定义路由数据值。 这将运行,但它会引发异常,因为在运行时 HttpContext.Current 为空。 我在想,因为它在运行时没有解决每个请求。

kernel.Bind<IMyContext>().To<MyContext>().InRequestScope();
kernel.Bind(x => x
.FromThisAssembly()
.SelectAllClasses()
.InheritedFrom(typeof(IQueryHandler<,>))
.StartingWith(kernel.Get<IMyContext>().customRouteValue)    // this isn't valid...
.BindSingleInterface()
.Configure(b => b.InRequestScope())
);

有没有办法使用"ToMethod"或工厂/提供程序机制来移动用于匹配运行时特定值的逻辑并根据命名约定返回具体类型? 或者还有其他想法来实现这一点吗?

更新:我使用以下模式进行数据库访问:https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=92

所以我有一个 IQueryHandler<,> 的实现,用于对我的数据库的每种查询类型。

IQueryHandler<GetDocInfo, DocInfo>
IQueryHandler<GetFileInfo, FileInfo>
IQueryHandler<GetOrderInfo, OrderInfo>
IQueryHandler<GetMessageInfo, MessageInfo>

我的确切问题是,跨客户端的某些表具有不同的架构,因此我必须根据 url 中的路由配置覆盖某些客户端的实现。

public class defaultschemaGetMessageQueryHandler : IQueryHandler<GetMessageInfo, MessageInfo>
public class client1schemaGetMessageQueryHandler : IQueryHandler<GetMessageInfo, MessageInfo>
public class client2schemaGetMessageQueryHandler : IQueryHandler<GetMessageInfo, MessageInfo>

我感兴趣的另一个地方是覆盖特定的查询实现以从不同的数据存储中提取:API 或 NoSQL。

更新 2最终更新。 因此,我采用了下面的代码并进行修改,以从命名方案转向基于属性,因为我不希望每个IQueryable为每个不同的默认类型命名为"QueryHandler"。

更改了此内容:

string route = serviceType.Name.Substring(0, indexOfSuffix);

对此:

string route = System.ComponentModel.TypeDescriptor
  .GetAttributes(serviceType)
  .OfType<QueryImplementation>()
  .Single()
  .Id;

并添加了以下属性,我正在使用该属性来装饰我的 IQueryHandlers

[System.AttributeUsage(System.AttributeTargets.Class |
    System.AttributeTargets.Struct)
]
public class QueryImplementation : System.Attribute
{
    public string Id { get { return id; } }
    private string id;
    public QueryImplementation(string id)
    {
        this.id = id;
    }
}

像这样使用:

[QueryImplementation("Custom")]
public class CustomDocQueryHandler : IQueryHandler<GetDocInfo, DocInfo>

然后只需要为我的"默认值"做同样的事情,以按属性而不是名称获取。

Ninject 基于约定的绑定将在运行时解析

因此,让我为您提供一种实现它的方法。关键字是上下文绑定。

(但请注意,性能明智的上下文绑定成本相当高,因为条件经常被评估。对于一个庞大的网络应用程序来说,这可能是一个问题......

您已经了解了约定的第一部分,让我用上下文绑定魔法对其进行修改:

kernel.Bind(x => x
    .FromThisAssembly()
    .SelectAllClasses()
    .InheritedFrom(typeof(IQueryHandler<,>))
    .BindSingleInterface()
    .Configure(QueryHandlerBindingConfigurator.Configure));
public class QueryHandlerBindingConfigurator
{
    private static readonly string DefaultImplementationName =
        RetrieveDefaultImplementationName();
    public static void Configure(
        IBindingWhenInNamedWithOrOnSyntax<object> syntax,
        Type serviceType)
    {
        if (!IsDefaultImplementation(serviceType))
        {
            int indexOfSuffix = serviceType.Name.IndexOf(
                                  DefaultImplementationName,
                                  StringComparison.InvariantCultureIgnoreCase);
            if (indexOfSuffix > 0)
            {
                // specific handler
                string route = serviceType.Name.Substring(0, indexOfSuffix);
                syntax.When(x => route == 
                          syntax.Kernel.Get<IMyContext>().CustomRouteValue);
            }
            else
            {
                // invalid name!
                throw CreateExceptioForNamingConventionViolation(serviceType);
            }
        }
        syntax.InRequestScope();
    }
    private static bool IsDefaultImplementation(Type serviceType)
    {
        return serviceType.Name.StartsWith(
                   DefaultImplementationName,
                   StringComparison.InvariantCultureIgnoreCase);
    }
    private static Exception CreateExceptioForNamingConventionViolation(
        Type type)
    {
        string message = String.Format(
            CultureInfo.InvariantCulture,
            "The type {0} does implement the {1} interface, " +
                "but does not adhere to the naming convention: " +
            Environment.NewLine + "-if it is the default handler, " +
                 "it should  be named {2}" +
            Environment.NewLine + "-if it is an alternate handler, " +
                 "it should be named FooBar{2}, " +
                 "where 'FooBar' is the route key",
            type.Name,
            typeof(IQueryHandler<,>).Name,
            DefaultImplementationName);
        return new ArgumentOutOfRangeException("type", message);
    }
    private static string RetrieveDefaultImplementationName()
    {
        // the name is something like "IQueryHandler`2",
        // we only want "QueryHandler"
        string interfaceName = typeof(IQueryHandler<,>).Name;
        int indexOfApostrophe = interfaceName.IndexOf(
               "`",
               StringComparison.InvariantCulture);
        return interfaceName.Substring(1, indexOfApostrophe - 1);
    }
}

我已经用以下方法进行了测试:(使用 XUnit 和 FluentAssertions(

public class Test
{
    [Fact]
    public void Whoop()
    {
        var kernel = new StandardKernel();
        var contextMock = new Mock<IMyContext>();
        kernel.Bind<IMyContext>().ToConstant(contextMock.Object);
        kernel.Bind(x => x
            .FromThisAssembly()
            .SelectAllClasses()
            .InheritedFrom(typeof(IQueryHandler<,>))
            .BindSingleInterface()
            .Configure(QueryHandlerBindingConfigurator.Configure));
        contextMock.Setup(x => x.CustomRouteValue).Returns(string.Empty);
        kernel.Get<IQueryHandler<int, int>>()
              .Should().BeOfType<QueryHandler>();
        contextMock.Setup(x => x.CustomRouteValue).Returns("AlternativeOne");
        kernel.Get<IQueryHandler<int, int>>()
              .Should().BeOfType<AlternativeOneQueryHandler>();
        contextMock.Setup(x => x.CustomRouteValue).Returns("AlternativeTwo");
        kernel.Get<IQueryHandler<int, int>>()
              .Should().BeOfType<AlternativeTwoQueryHandler>();
    }
}