Ninject Convention Based绑定在运行时解析

Ash*_*Lee 5 .net c# dependency-injection ninject

我正在使用命令处理程序模式并与ninject.extensions.Conventions绑定,当我的实际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();
Run Code Online (Sandbox Code Playgroud)

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

    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()
)
Run Code Online (Sandbox Code Playgroud)

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

我希望完成的是使用类似下面的内容.我已经实现了一个类/接口来返回自定义的Route数据值.这会运行,但它会抛出异常,因为在运行时HttpContext.Current为null.我在想,因为它不能在运行时解析每个请求.

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

有没有办法使用"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>
Run Code Online (Sandbox Code Playgroud)

我的确切问题是我对客户端的某些表有不同的模式,因此我必须根据URL中的Route Config覆盖某些客户端的实现.

public class defaultschemaGetMessageQueryHandler : IQueryHandler<GetMessageInfo, MessageInfo>
public class client1schemaGetMessageQueryHandler : IQueryHandler<GetMessageInfo, MessageInfo>
public class client2schemaGetMessageQueryHandler : IQueryHandler<GetMessageInfo, MessageInfo>
Run Code Online (Sandbox Code Playgroud)

我有兴趣使用它的另一个地方是覆盖一个特定的查询实现来从不同的数据存储区提取:API或NoSQL.

更新2 最后更新.所以我采用下面的代码并修改为从命名方案转移到基于属性,因为我不希望每个IQueryable都为每个不同的默认类型命名为"QueryHandler".

改变了这个:

string route = serviceType.Name.Substring(0, indexOfSuffix);
Run Code Online (Sandbox Code Playgroud)

对此:

string route = System.ComponentModel.TypeDescriptor
  .GetAttributes(serviceType)
  .OfType<QueryImplementation>()
  .Single()
  .Id;
Run Code Online (Sandbox Code Playgroud)

并添加了以下用于装饰我的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;
    }
}
Run Code Online (Sandbox Code Playgroud)

像这样使用:

[QueryImplementation("Custom")]
public class CustomDocQueryHandler : IQueryHandler<GetDocInfo, DocInfo>
Run Code Online (Sandbox Code Playgroud)

然后只需要为我的"默认"做同样的事情来获取属性而不是名称.

Bat*_*nit 3

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

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

您已经获得了约定的第一部分,让我用上下文绑定魔法修改它:

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

我已经对其进行了测试:(使用 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>();
    }
}
Run Code Online (Sandbox Code Playgroud)