Reputation: 23070
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 would 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();
}
}
}
Upvotes: 19
Views: 9938
Reputation: 1054
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)
Upvotes: 0
Reputation: 32725
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
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:
Upvotes: 22
Reputation: 133
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.
Upvotes: 6
Reputation: 122654
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.
Upvotes: 5