我正在使用命令处理程序模式并使用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();
但我遇到过一个场景,我需要根据自定义路由值在运行时覆盖默认的具体类型。以下工作没有问题:
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&lt;,&gt;中的每一个编写此代码。通用类型。另外,对于每个我想要全局应用的装饰器(如顶部样本),我将不得不修改每个绑定并添加它,使代码加倍或增加三倍。
我希望完成的是使用以下内容。我已经实现了一个类/接口来返回自定义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())
);
有没有办法使用&#34; ToMethod&#34;或者工厂/提供程序机制来移动逻辑以匹配运行时特定值并根据命名约定返回具体类型?或者任何其他想法来实现这个目标?
更新:我使用以下模式进行数据库访问:https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=92
所以我有一个IQueryHandler的实现&lt;,&gt;对于我的数据库的每种类型的查询。
IQueryHandler<GetDocInfo, DocInfo>
IQueryHandler<GetFileInfo, FileInfo>
IQueryHandler<GetOrderInfo, OrderInfo>
IQueryHandler<GetMessageInfo, MessageInfo>
我的确切问题是我对客户端的某些表有不同的模式,因此我必须根据URL中的Route Config覆盖某些客户端的实现。
public class defaultschemaGetMessageQueryHandler : IQueryHandler<GetMessageInfo, MessageInfo>
public class client1schemaGetMessageQueryHandler : IQueryHandler<GetMessageInfo, MessageInfo>
public class client2schemaGetMessageQueryHandler : IQueryHandler<GetMessageInfo, MessageInfo>
我对使用它感兴趣的另一个地方是覆盖特定的查询实现以从其他数据存储区提取:API或NoSQL。
更新2 最后更新。所以我采用下面的代码并修改为从命名方案转移到基于属性,因为我不希望每个IQueryable被命名为&#34; QueryHandler&#34;对于每种不同的默认类型。
改变了这个:
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>
然后我必须为我的&#34;默认&#34;做同样的事情。通过Attribute而不是Name获取。
答案 0 :(得分:3)
那么,让我为您提供一种如何实现它的方法。关键字为contextual binding。
(但请注意,性能明智的上下文绑定相当昂贵,因为条件经常被评估。对于大规模的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);
}
}
我用以下方法对其进行了测试: (使用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>();
}
}