views:

375

answers:

2

I want to use conditional binding in ninject, based on passed parameters. I have something like below:

public class Subject
{
}

public interface ITarget
{
}

public class Target1 : ITarget
{
}

public class Target2 : ITarget
{
}

And now I need to instantiate ITarget interface:

    public void MethodName(IKernel kernel)
    {
        ITarget target1 = kernel.Get<ITarget>(new Parameter("name", new Subject(), true)); // Should be instance of Target1
        ITarget target2 = kernel.Get<ITarget>(); // Should be instance of Target2
    }

I have problems to define proper bindings. I tried the following:

kernel.Bind<ITarget>().To<Target1>().When(Predicate);
kernel.Bind<ITarget>().To<Target2>();

private bool Predicate(IRequest request)
{
    IParameter parameter = request.Parameters.Count == 0 ? null : request.Parameters[0];
    if (parameter == null)
    {
        return false;
    }
    object parameterValue = parameter.GetValue( /*what to put here?*/);
    return parameterValue != null && parameterValue.GetType().IsAssignableFrom(typeof(Subject));
}

but I don't know how to get value of passed parameter. I need to pass IContext instance to GetValue method, but don't know how to get valid instance of IContext. Or maybe there is better way to accomplish my task?

EDIT: BindingMetadata is better way to solve my problem. See http://stackoverflow.com/questions/2507881/contextual-bindings-with-ninject-2-0 for details

Regards

+1  A: 

Does IRequest.ParentContext not give you enough ?

Also, if all you are looking for is to use a Name to disambiguate multiple instances [and you'll be in a position to provide it each time and are happy to work in that explicit Service Location manner instead of using DI as IDeity intended], then there is a Get overload with a name and a WithName for on the Bind side.

EDIT: Spent some time in Reflector but am still not much wiser. The parameters mechanism seems to be for the purposes of overriding specific values in the context of an actual binding happening, not as part of selecting the binding that applies. Establishing a context is only done in one place internally (though Context has a public ctor) so it doesnt look like there's a neat factory for that.

Does your actual scenario happen to fit the constraint mechanism (of which using a name to filter is just the simplest case). i.e., can you express your Get as something like:

_kernel.Get<ITarget>( (Ninject.Planning.Bindings.BindingMetadata metadata)=>metadata.Has("x") );

Hopefully @Ian Davis will be along soon as I'd love to know the answer too :D

Ruben Bartelink
Unfortunately, ParentBinding property is null here. I need something like CurrentContext, but there is no such property unfortunately.I know and use named bindings, but I really need to utilize parameters in production code. My sample code in this query is simplified for brevity.
Przemaas
ParentContext, not ParentBinding of cource
Przemaas
As you proposed, BindingMetadata was better way to solve my problem.
Przemaas
@Przemaas: Delighted I managed to hit upon the answer during my travels. BTW the non-null check on the context param is baked in at a low level to the GetValue call yet the two most common concrete implementaitons dont make use of it. But I'm sure this is by design as we'd concluded.
Ruben Bartelink
+1  A: 

IIRC a request can be part of several contexts. The parameters you see are only parameters that you specify in the Get call. In the example you give, you should be able to pass null as the IContext in order to get your subject instance. Since you supply an object instead of a callback, the parameter constructs a callback as ctx => value -> thus the callback doesn't use the context value you supply.

Ian Davis
There is null check for context value, at least in officially released binaries. I didn't check code in trunk. So when I pass null, I get Exception. Should I file a bug for this?
Przemaas
use parameter.ValueCallback(null), GetValue does a null check, sorry.
Ian Davis
That involves ugly casting, because ValueCallback is not member of IParameter, but it works - more or less. Now I get exceptions when instantiating target2, but this is not related to current issue. Will ask another question for this
Przemaas
@Ian Davis: As I mentioned in my comment to @Przemaas in the comments on my answer, the GetValue forces it yet the only two concrete parameter types dont use it. It'd be nice to see some guidance or change on this in the exception message or in the docs.
Ruben Bartelink
@Ruben, Parameter does use the context to resolve the value by passing it to the callback function, but after checking if the context is null. Are you wanting guidance of when to use the .When(Func<IRequest,bool> predicate) vs the metadata binding? What would you want an exception to say? Thanks!
Ian Davis
@Ian Davis: I was referring to the fact that the impls for Prop and Constructor Arg both dont use the param they are given. Re exception message, the .GetValue should not say "make it not nuLL" but instead say "make it not null because we guarantee that to x and y for z". I GTGN but will re-reflector int he AM and restate this if this doesnt make sense (which is quite likely!)
Ruben Bartelink
@Ruben, sorry, you lost me. Also, why are you trying to use reflector on an open source library? Do you want to take this conversation to email instead?
Ian Davis
@Ian Davis: Reflector is always open on my box - it's all 'open' to me :P Seriously though, got source and its nice as I'd expect (also have source for xUnit and appreciate and use that as being 'the source'). My point is that `public Parameter(string name, object value, bool shouldInherit) : this(name, ctx => value, shouldInherit) { }` isnt using `ctx` and its the most common way a ValueCallback gets created. The fact that the callers of ValueCallback are requiring ctx!=null [and effectively guaranteeing ctx != null to the callback impl] isnt being utilised by the most common impls.
Ruben Bartelink
Re changing the exception message - if GetValue is intended to be used by 'user' code such as day-to-day impls of a OnlyWhen Predicate, then Ensure.ArgumentNotNull (basic explict DbC enforement) isnt enough - you want to provide the user an exception that says 1. low level thing that went wrong 2. why this sort of thing can happen 3. how to get around that. In this case its unclear to me that the predicate mechanism is a core day-to-day user mechanism. But it definitely isnt obvious to a user as-is. Perhaps an article covering intended usage might remove the confusion [in my head] around this.
Ruben Bartelink
Finally, sorry for continuing this rant on SO. I follow the NInject tag on SO and try to help. As I get back to using NI for work I'll get around to getting on the mailing list - promise.
Ruben Bartelink
This page is an uncommon usage scenario. The context is meant to allow the user to request an object using the kernel or other context bound information in the callback - this is only intended to be used by the kernel. This type of scenario is better off using meta data.
Ian Davis