开发者

Ninject default contextual binding

开发者 https://www.devze.com 2023-03-06 06:32 出处:网络
I have an interface with a few different concrete implementations. I am trying to give Ninject a default to use and only use the other implementation if a name matches. For instance, I have the follow

I have an interface with a few different concrete implementations. I am trying to give Ninject a default to use and only use the other implementation if a name matches. For instance, I have the following bindings.

Bind<ISomething>().To<DefaultSomething>()
Bind<ISomething>().To<OtherSomething>().Named("55abd8b8-097f-4e1c-8d32-95cc97910604");

What I w开发者_StackOverflowould like is if the Named section doesn't match, to use the DefaultSomething implementation. When I pass in the explicitly bound guid, it works fine. When I pass in any other guid I get the "No matching bindings are available" exception.

Bind<ISomething>().To<OtherSomething>().Named("55abd8b8-097f-4e1c-8d32-95cc97910604");
Bind<ISomething>().To<DefaultSomething>()

Bind<ISomething>().To<DefaultSomething>()
Bind<ISomething>().To<OtherSomething>().When(ctx => ctx.Service != null && ctx.Service.Name == "55abd8b8-097f-4e1c-8d32-95cc97910604");

I have also tried using .When to check the binding and I have tried reversing the order like below however I am never able to bind unless I pass in the Guid that is explicitly named.

This article seems to indicate that default bindings work, so I must be doing something wrong. Any suggestions?


Edit: Here is a complete example showing the problem I am trying to solve. The desired behavior is for kernel.Get<INumber>("Three").Write() to return "Unknown Number"

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ninject;

namespace NinjectTest
{
    interface INumber
    {
        string Write();
    }

    class UnknownNumber : INumber
    {
        public string Write()
        {
            return "Unknown Number";
        }
    }

    class One : INumber
    {
        public string Write()
        {
            return "1 = One";
        }
    }

    class Two : INumber
    {
        public string Write()
        {
            return "2 = Two";
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            StandardKernel kernel = new StandardKernel();
            kernel.Bind<INumber>().To<UnknownNumber>();
            kernel.Bind<INumber>().To<One>().Named("One");
            kernel.Bind<INumber>().To<Two>().Named("Two");

            Console.WriteLine(kernel.Get<INumber>("One").Write());
            Console.WriteLine(kernel.Get<INumber>("Two").Write());
            Console.WriteLine(kernel.Get<INumber>("Three").Write());

            Console.ReadLine();
        }
    }
}


You completely missunderstood named bindings:

Giving a binding a name is NOT a condition. You will still get all of them when requesting them without a constraint. Adding a name changes absolutely nothing on its own.

Requesting an instance using a name adds the constraint:

only bindings whose name matches the given one shall be returned

In your case, you gave me an instance whose binding's name is "three". And you expect it to return UnknownNumber, which does not even have a name.

This can be achieved by either

  1. passing a parameter and adding conditions to the bindings that check if the parameter matches, or
  2. passing a constraint that fits the name or the unnamed instance and declare the unnamed one implicit.

Option 1:

public class CustomerIdParameter : Parameter
{
    public CustomerIdParameter(string id) : base("CustomerId", (object)null, false)
    {
        this.Id = id;
    }
    public string Id { get; private set; }
}

kernel.Bind<ISomething>().To<Default>();
kernel.Bind<ISomething>().To<Other>()
      .When(r => r.Parameters.OfType<CustomerIdParameter>()
                             .Single().Id == "SomeName");

kernel.Get<IWeapon>(new CustomerIdParameter("SomeName")).ShouldBeInstanceOf<Sword>();

I leave it up to you to write the extension methods to make the definition and resolve easier.

Option 2:

Bind<ISomething>().To<Default>().Binding.IsImplicit = true;
Bind<ISomething>().To<Other>().Named("SomeName")

public static T GetNamedOrDefault<T>(this IKernel kernel, string name)
{
    return kernel.Get<T>(m => m.Name == null || m.Name == name);
}

But honestly I think what you want to do doesn't seem to be a proper design:

  1. Keep your access to the kernel to an absolute minimum. What you're doing here is a ServiceLocator-like usage of Ninject.
  2. If no binding is available for an expected instance, I'd rather expect an exception than using a default instance because this is a bug.


You can also simply add a condition for your binding to not have a condition, like so:

kernel.Bind<IObject>().To<Object1>().When(
           x => x.ParentContext != null && !x.ParentContext.Binding.IsConditional)
          .InRequestScope();

kernel.Bind<IObject>().To<Object2>().InRequestScope()
          .Named("WCFSession");

When doing a standard Inject without a Name specified, the first binding will be used. When specifying a name, the named binding will be used. It's not the prettiest solution, but it works.


It's quite possible to do this in Ninject, it just doesn't happen to be the way the resolution behaves by default. The IKernel.Get<T> extension does not ask for the "default" binding, it asks for any binding; in other words it does not apply any constraints. If there is more than one matching binding then it throws an exception to that effect.

Try out these two extension methods:

static class KernelExtensions
{
    public static T GetDefault<T>(this IKernel kernel)
    {
        return kernel.Get<T>(m => m.Name == null);
    }

    public static T GetNamedOrDefault<T>(this IKernel kernel, string name)
    {
        T namedResult = kernel.TryGet<T>(name);
        if (namedResult != null)
            return namedResult;
        return kernel.GetDefault<T>();
    }
}

The first one gets the "default" binding - i.e. whichever one you've bound that has no name. The second one tries to get a named binding, but if it doesn't find that, then it reverts to the default.


Of course, Remo is not wrong either; you should avoid using Ninject or any other container this way unless you have a particularly good reason to. This is the Service Locator (anti-)pattern, not true dependency injection. You should be using the When syntax for conditional bindings, either using complex conditions or just decorating the classes which need special bindings, i.e.:

Bind<IFoo>().To<SpecialFoo>().WhenInjectedInto<ClassThatNeedsSpecialFoo>();

or...

Bind<IFoo>().To<SpecialFoo>().WhenMemberHas<SpecialAttribute>();

class InjectedClass
{
    public InjectedClass([Special]IFoo) { ... }
}

That is the right way to handle default and conditional bindings. Named bindings are really only useful when you're trying to implement a factory pattern and you want to wrap the IoC container in your custom factory. That's OK, but please use it sparingly, as you end up throwing away many/most of the benefits of dependency injection that way.


Alternatively, you could actually implement your own activation behaviour and use it to override the default in Ninject - everything is modular and gets shoved into the "Components" collection. But this is not for the faint of heart, so I don't plan on including a detailed tutorial here.


I checked the ParaSwarm's solution and it worked for a simple test project. But in a real project his solution didn't fit. I managed to solve it by the following code:

const string specificServiceName = "For" + nameof(SpecificService);
kernel.Bind<IService>()
    .To<DefaultService>()
    .When(x => x.Constraint == null || !x.Constraint(new BindingMetadata { Name = specificServiceName }))
    .InTransientScope();

kernel.Bind<IService>()
    .To<SpecificService>()
    .InTransientScope()
    .Named(specificServiceName);

PS: my answer does not solve the author's problem, but it can help someone with searching similar problem (as ParaSwarm's answer helps me)

0

精彩评论

暂无评论...
验证码 换一张
取 消