5

I'm playing around with Ninject for a simple test-bed project at home, just to see what I can do with it. As a starting point I'm building a console runner for some service, which accepts a variety of arguments and based on what it gets in, uses the same methods provided for a fluent interface to configure a model to run.

As an example, suppose I have a verbosity switch, /o. /o can be passed as /o:quiet, /o:normal, or /o:verbose. The various options are self-explanatory.

To satisfy this argument I would like to attach various implementations of ILogger - quiet gets a quiet logger that prints only critical messages, normal gets a normal logger, and verbose gets a chatty logger that prints everything.

What I'd like to do is something in a module like:

Bind<ILogger>().To<QuietLogger>().When(VerbosityParameter=="quiet");
Bind<ILogger>().To<VerboseLogger>().When(VerbosityParameter=="verbose");

...and so on.

I can't see how to do anything like this; all the conditional bindings seem to be dependent on the state of the injection target. What's the point of that? Doesn't it defeat the entire point of dependency injection when the consuming class has to specify in exact detail all the conditions needed to determine what concrete type it gets given? Why can't I just tell Ninject what I want, and get it?

Tom W
  • 5,108
  • 4
  • 30
  • 52
  • I realize this question is ancient, but I think I ran into a similar issue just recently, and finally got Ninject to (sort of) behave the way I wanted by using `ToMethod` for binding, and then passing a Ninject Parameter to the kernel's `Get`. This gave me access to the context along w/ parameter value as I needed. http://stackoverflow.com/questions/22766200/cant-get-value-of-ninject-constructorargument-passed-in-as-parameter-to-kernel/22812501#22812501 – Brett Rossier Apr 02 '14 at 13:17

2 Answers2

4

The ctx parameter is just one input into the contextual binding - there's nothing saying you need to pay the slightest bit of attention to it (except you need to be signature compatible with the delegate signature).

Bear in mind the RRR pattern though and don't go crazy.

IOW you need to be (in V2 syntax doing it):

Bind<IWarrior>().To<Samurai>().When(_ => expression not involving context at all);

(Where _ is a poor man's pidgin use of the F# pattern matching syntax for ignoring inputs)

Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
  • I'd missed the paragraph in your link: _"alternately, you can programmatically request a specificly named instance of a service directly (but be careful – doing so directly is typically an instance of the Service Location antipattern) by doing: kernel.Get("Weak");"_ This looks more like what I had in mind - but why is it an antipattern? In what possible sense is it an antipattern to retrieve varying concrete types based on runtime parameters from something that is **designed** to encapsulate instance construction and retrieval? – Tom W Dec 06 '11 at 18:28
  • Having said the above, I would _prefer_ to resolve the Configuration instance with some sort of argument which dictates what the DI framework should be doing to create the configuration. If you can't make changes at runtime based on user input, what's the point? – Tom W Dec 06 '11 at 18:32
  • The point being made in the docs is that any call to `Get<>` except in the middle R bit within a Composition root is a) not discernable from ctor args only b) coupling to a container. The preferred approach is to drive such creation through an injected auto-generated abstract Factory (see http://stackoverflow.com/questions/4840157/does-ninject-support-func-auto-generated-factory) or one that you explicitly implement near your composition root to which you supply any parameters and then do the (potentially named) `Get` request against the Kernel in the Factory rather than in your service class. – Ruben Bartelink Dec 07 '11 at 13:29
  • 1
    I think this is my best option. A Factory would be the traditional way to encapsulate logging creation and there's nothing stopping Ninject from returning one of those, but I had hoped that using a DI framework would minimise the amount of factory cruft I had to write/maintain. – Tom W Dec 09 '11 at 08:23
4

In this special case I wouldn't replace the logger instance but rather configure your logging framework to log exactly what you want to.

Also the When condition does not depend on the target you can put there any kind of condition. E.g.

When(ctx => Configuration.Get("VerborsityLevel") == "quiet")
Remo Gloor
  • 32,665
  • 4
  • 68
  • 98
  • Thanks for having a stab at this. In hindsight I wasn't as clear as I could have been - `VerbosityLevel` in my mind ought not to be a parameter on the Configuration instance, for the reason I mentioned in my post - this makes the configuration responsible for dictating the concrete type, defeating the point of DI. – Tom W Dec 06 '11 at 18:26
  • Configuration in my example isn't the App config but the configuration values passes by the console arguments. Just provide something where the conditions can ask whether they apply. But as I said. It's much better in this case to change the verbosity by changing the logging framework configuration. This can also be done in code. – Remo Gloor Dec 06 '11 at 19:45
  • This presents the same question - **How** should I change the logging framework configuration at runtime in a way that is idiomatic to DI frameworks? – Tom W Dec 06 '11 at 19:47
  • I cannot see where this is related to an IoC framework. The only thing I would do is to do some calls to the logging framework to configure it to log only what is wanted and use the same logger independent of the console params. How this is done is written in the doc of your logging framework. e.g. log4net let you setup filters and appenders like required – Remo Gloor Dec 06 '11 at 21:35
  • 1
    Behaviour ought to be encapsulated, and runtime-configurable behaviour implies multiple services implementing a contract. Are we saying now that the choice of service implementer in the DI code should **only** be configured at compile-time? To emphasise, there is nothing in my code that allows a DI framework to distinguish **at compile time** which implementation is required, by necessity. Does it necessarily follow that a DI framework cannot directly be responsible for these services? – Tom W Dec 09 '11 at 08:21
  • Once again. I'd take a completely different approach not related to DI to achieve the same thing as you want. In the special case of logging I would not change the behavior by changing the implementation of the logger. But I would do this by changing the configuration of the logging framework so that it logs exactly what was requested by the console arguments. The configuration can be changed using the API of the logging framework. – Remo Gloor Dec 09 '11 at 19:59
  • 1
    Ignore for the moment that the example I chose is a Logger. In fact, I wrote `ILogger` and `DefaultLogger : ILogger` _after_ posting this question to explore the examples given. The service in question could be anything, so substitute the particular case of a logging service for any that you'd imagine **would** be appropriate to configure by multiple implementers. – Tom W Dec 09 '11 at 20:36