Ninject-based binding that must be allowed at runtime

I use the Command Handler pattern and bind to ninject.extensions.Conventions, which works fine when my actual implementation of the IQueryHandler <,> interface matches one particular type. Here is what I use:

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(); 

But I came across a scenario where I need to override a specific default type at runtime based on a custom route value. The following works without problems:

  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() ) 

The problem with the above is that I will need to write this code for each of my IQueryHandler <,> generic types. In addition, for each decorator that I would like to apply around the world (for example, in the upper sample), I would have to change each binding and add it, double or triple the code.

What I hope to do is use something like the following. I have implemented a class / interface to return the value of user route data. This is executed, but it throws an exception, because at run time, HttpContext.Current is null. I think because it does not allow the request at runtime.

 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()) ); 

Is it possible to use "ToMethod" or the Factory / Provider mechanism to move the logic to match a specific runtime value and return a specific type based on a naming convention? Or any other ideas for this?

UPDATE I use the following template to access the database: https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=92

So, I have an implementation of IQueryHandler <,> for each type of query in my DB.

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

My specific problem is that I have different schemas for specific tables between clients, so I have to redefine the implementation for specific clients based on Route Config in the url.

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

Another place I'm interested in using it would be to override a particular query implementation to retrieve from another data store: API or NoSQL.

UPDATE 2 Final update. So I took the code below and changed it to go from an attribute-based naming scheme, because I don't want every IQueryable to be called "QueryHandler" for every other type by default.

Modified by:

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

For this:

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

And added the following attribute that I use to decorate my 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; } } 

Used as follows:

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

Then just had to do the same for my "default" to get the attribute instead of the name.

+5
source share
1 answer

So let me give you one way you can achieve this. Keyword contextual binding .

(Note, however, that the performance of wise context bindings is quite expensive, as conditions are often evaluated. For a massive web application, this can be a problem ...)

You already have the first part of the convention, let me fix it with context binding:

 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); } } 

I tested it with: (using XUnit and 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>(); } } 
+3
source

Source: https://habr.com/ru/post/1208259/


All Articles