3

I am using .NET 4.5, Ninject 3 with the binding by convention lib as follow:

kernel.Bind(x => x
    .FromAssembliesMatching("assembly.dll")
    .SelectAllClasses().InheritedFrom(typeof(ICommandHandler<>))
    .BindAllInterfaces());

And this is binding properly for:

public class MyCommandHandler : ICommandHandler<MyCommand>

But doesn't bind:

public class MyGenericCommandHandler<T> : ICommandHandler<MyGenericCommand<T>>

However, the previous binding works if I add individual bindings for specific implementation of my generics class, such as:

kernel.Bind(typeof(ICommandHandler<MyGenericCommand<float>>))
      .To(typeof(MyGenericCommandHandler<float>))
kernel.Bind(typeof(ICommandHandler<MyGenericCommand<int>>))
      .To(typeof(MyGenericCommandHandler<int>))

But adding each individual generic type defeats the purpose of conventions and require adding binding for each possible individual type such as float, int, string, etc...

Do you know how to modify the convention or add another one (or even come with a completely different solution) to support the generic version of my command? i.e. supporting two-level generics.

Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
Adam
  • 3,872
  • 6
  • 36
  • 66
  • It doesn't sound likke you're doing anything wrong or crazy, buty it's not clear to me what you want. An open generic class can't be instantiated, but you can do [stuff like this](http://stackoverflow.com/questions/11682495/ninject-and-binding-generics). Can you clarify what `TParameter` is, how you're specifying the `Type` you want, and it's no harm to put in more of the exception. – Ruben Bartelink May 27 '13 at 08:26
  • @RubenBartelink I did improve the question and gave a more specific example. I hope this answers your question. – Adam May 27 '13 at 11:11
  • 1
    Do you have a class that derives from [and hence implements] `ICommandHandler>` coz the message says you don't ? Is it public? Is the open generic with the type necessary? Typically people have one level of open generic - the `ICommandHandler<>` and then have a concrete class implementing that. Unfortunately I'm more confused now but hopefully someone else can see what you're trying to do – Ruben Bartelink May 27 '13 at 11:41
  • @RubenBartelink, I did simplify the question and thank you for trying to help. I removed redundant explanation. – Adam May 27 '13 at 13:50
  • @Adam: Are you hooked to Ninject, or do you mind if I answer your question with how to do this with Simple Injector? – Steven May 28 '13 at 12:27
  • I have everything linked with Ninject, sorry. – Adam May 28 '13 at 18:41
  • possible duplicate of [NInject with Generic interface](http://stackoverflow.com/questions/2216127/ninject-with-generic-interface) – nawfal Nov 05 '13 at 04:49

1 Answers1

0

EDIT: Doesn't compile [and the fact it doesn't reveals the requirement doesn't make sense], see reasoning for this conclusion in the comments.


This is just the normal open generics case. As alluded to with a link in my earlier comment, the way a basic bind to make that DRY looks is simply:

kernel.Bind(typeof(ICommandHandler<MyGenericCommand<>>))
    .To(typeof(MyGenericCommandHandler<>));

This binding will then suffice for any T variant of ICommandHandler<MyGenericCommand<T>>.

In the context of doing convention based binding it to mapping what you're expecting, the problem is that SelectAllClasses().InheritedFrom(typeof(ICommandHandler<>)) does not include generic classes -- this makes sense as it's rare that you can specify generalized rules on non concrete classes.

You can either

  • use a different Projection portion of the DSL to select the Generic classes and then bind them in some convention based manner
  • expose a NinjectModule from assemblies exposing [open] generic services which does the above Bind and then do a kernel.Load() of the module [prob by the DLL name pattern].
Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
  • The first two code lines that you've mentioned do not compile with an error message saying 'Type expected' this is why I had to supply the 'float' and 'int' in my example. – Adam May 27 '13 at 15:13
  • @Adam Right, the open generic syntax (and the .NET type system) does not have a way to express a type which has a *nested* type arg open. You need to resolve this by getting back to top level class that has an open arg, only. Bottom line here is that you have a Jon Skeet C# tag issue, not a Ninject issue. More importantly, you're straying way off the beaten path - your code is trying to tell you something... Two axes of genericity are rarely a good idea (kinda like MI...) – Ruben Bartelink May 27 '13 at 15:44
  • Thank you, but I wouldn't say 'straying way off the beaten path' as this is a standard CQRS pattern implementation with a generic command. However, I agree with you that having two levels of generic is a C# limitation and C++ Templates has this feature, so it is really a C# limitation and not my implementation. – Adam May 27 '13 at 15:56
  • 1
    @Adam 'it's in C++ so ...' isn't an easy sell on me :P Like MI, the constraint (which is a .NET constraint relating to representing the whole thing in a Reflectionable type graph) is actually a Good Thing. Having Command handlers and a generic `ICommandHandler` is normal CQRS. Having a `StandardCommand` and `NonStandardCommand` and being able to ask for a `ICommandHandler` or `ICommandHandler` via a uniform *is* off the beaten path -- I've been writing CQRS apps for some time and Ninject answers longer. – Ruben Bartelink May 27 '13 at 16:16
  • @Adam Bottom line is that in .NET this stuff is resolved at the lang/compiler level and isn't expressible as `Type`s so a) .NET doesn't do this b) F# will play your games and give you nice type inferencing c) C# doesnt do this level of type inference statically. But none of the above matters until you can actually explain a fundamental need for the handler to be generic. People don't do it, and you should find a way where you don't have to either. – Ruben Bartelink May 27 '13 at 16:21
  • You say people don't do it, people do CQRS, this is a standard CQRS pattern, however, it happened that the command in this case is a generic one. Who said that a command shouldn't be generic? I would say this is a subjective view. – Adam May 27 '13 at 17:04
  • @Adam I have no new information to give, and it seems the same your side. If you have more significant detail, we can move forward. If you want to refer to all the people who are using DI with random parts of the concrete types open, go ahead - should be easy as it is allegedly a very common pattern in C++ and/or CQRS. But if you just want to feel [you've 'won' a debate on The Internet](http://xkcd.com/386/), I shan't be standing in the way! – Ruben Bartelink May 27 '13 at 20:03
  • It is not about winning a debate, however, the claim "People don't do it" implies that I went with my own framework and started reinventing stuff and I am far away from that. So, I had to respond. The fact that you went back and didn't ignore it, shows passion from your side :) Thank you for the reply. – Adam May 27 '13 at 21:21
  • @Adam What am I'm claiming? I know (and have impld) CQRS stuff with Ninject so please feel free to talk in specifics; please re-read my comments as to why *it does not make sense* for the open bit of the generic to be something other than the Command Type (and esp not a mix of that and other aspects) - people really don't do it - proof by counterexample? What framework are you using (no shame in adapting stuff BTW). In general, you've been responsive and your question is now excellently put. But you're not actually reading the content of my comments anymore which makes it all pretty pointless. – Ruben Bartelink May 27 '13 at 21:56
  • Lets agree to disagree :) I changed my implementation to passing an `object` rather than using a generic which solves my problem. – Adam May 27 '13 at 22:06
  • Phew - now there's an approach that we can all agree *lots* of people use. Thankfully the compiler seemingly was able to get its point across in the end :P – Ruben Bartelink May 27 '13 at 22:15
  • @RubenBartelink: Can't you simply do `kernel.Bind(typeof(ICommandHandler<>)) .To(typeof(MyGenericCommandHandler<>));` in Ninject? Ninject should be able to spot the generic type constraint and find out when to resolve the type and when not. This is the equivalent in Simple Injector and that works: `container.RegisterOpenGeneric(typeof(ICommandHandler<>), typeof(MyGenericCommandHandler<>));`. – Steven May 28 '13 at 09:53
  • @steven Yes, what you are saying can be done and it works as with other containers. The problem is that the OP wants the open bit of the open generic to be two levels down - i.e. have a handler implementing `ICommandHandler>` to fulfil a `Resolve()` for `MyCommand`. While a container could theoretically do all sorts of type canonicalizations and/or walks to make this Just Work, it would be pretty pointless. Bottom line is the snippet I have in my answer does not compile because .NET can't represent a Type with the open bit not on the top level (and C# thankfully doesnt shim it) – Ruben Bartelink May 28 '13 at 11:57