2

I have two bindings:

Bind<ICache>().ToMethod(ctx => FactoryMethods.CreateCache(
        ctx.Kernel.Get<IXXX>(),
        ctx.Kernel.Get<IYYY>()))
    .WhenTargetHas<SharedCacheAttribute>()
    .InSingletonScope()
    .Named(BindingNames.SHARED_CACHE);

Bind<ICache>().ToMethod(ctx => FactoryMethods.CreateTwoTierCache(
    ctx.Kernel.Get<ICache>(BindingNames.SHARED_CACHE),
    ctx.Kernel.Get<IZZZ>()))
    .InSingletonScope();

Essentially the idea is that I have a shared cache (defined in first binding) but most of the time I want classes to use a two-tiered cache which is the same interface (ICache). I therefore restrict use of the shared cache using an attribute constraint (classes that need direct access to the shared cache can just use [SharedCache]).

Now, the problem is that the second binding, specifically this line:

ctx.Kernel.Get<ICache>(BindingNames.SHARED_CACHE),

is throwing an exception that no matching bindings are available, presumably because of the attribute constraint on the first binding.

How can I inject the resolution result of the first binding into the second binding's factory method?

Workaround:

Currently I am using a Parameter and a more complex When()-based constraint on the first binding. My bindings now look like this:

Bind<ICache>().ToMethod(ctx => FactoryMethods.CreateCache(
        ctx.Kernel.Get<IXXX>(),
        ctx.Kernel.Get<IYYY>()))
    .When(o => (o.Target != null && 
        o.Target.GetCustomAttributes(typeof (SharedCacheAttribute), false).Any()) ||
        o.Parameters.Any(p => p.Name == ParameterNames.GET_SHARED_CACHE))
    .InSingletonScope();

Bind<ICache>().ToMethod(ctx => FactoryMethods.CreateTwoTierCache(
    ctx.Kernel.Get<ICache>(new Parameter(ParameterNames.GET_SHARED_CACHE, true, true)),
    ctx.Kernel.Get<IZZZ>()))
    .InSingletonScope();

It works as intended, but the syntax is crazy complicated. Also, I would have expected the 'shouldInherit' argument of the Parameter constructor to have to be set to false to prevent the GET_SHARED_CACHE parameter from being passed to child requests. As it happens, setting this to false ends up causing a StackOverflowException as the parameter is persisted across requests when this is set to false. Setting it to true causes it to not propagate - the opposite of what I would have expected.

1 Answers1

2

An alternative would be to replace the SharedCacheAttribute by the NamedAttribute. Here's an example:

//bindings
Bind<ICache>().ToMethod(ctx => FactoryMethods.CreateCache(
    ctx.Kernel.Get<IXXX>(),
    ctx.Kernel.Get<IYYY>()))
.InSingletonScope()
.Named(BindingNames.SHARED_CACHE);

Bind<ICache>().ToMethod(ctx => FactoryMethods.CreateTwoTierCache(
    ctx.Kernel.Get<ICache>(BindingNames.SHARED_CACHE),
    ctx.Kernel.Get<IZZZ>()))
.InSingletonScope();

// cache users
public class UsesSharedCache
{
    public UsesSharedCache([Named(BindingNames.SHARED_CACHE)] ICache sharedCache)
    {
    }
}

public class UsesDefaultCache
{
    public UsesDefaultCache(ICache defaultCache)
    {
    }
}

Another alternative would be an IProvider. The binding would look like this:

Bind<ICache>().ToProvider<CacheProvider>();

the CacheProvider would contain the logic to determine whether to retrieve the "default" or the shared cache. It would need to check for the attribute and then resolve and return the corresponding instance. So there would need to be two more named bindings for ICache:

Bind<ICache>().ToMethod(...).Named("default")
              .BindingConfiguration.IsImplicit = true;
Bind<ICache>().ToMethod(...).Named("shared");
              .BindingConfiguration.IsImplicit = true;

Remark: .BindingConfiguration.IsImplicit = true; is necessary because otherwise ninject considers a request for ICache (without a name) to be fulfilled by all bindings - and throws an exception. The request needs to be fufilled by the provider, only.

BatteryBackupUnit
  • 12,934
  • 1
  • 42
  • 68
  • I think these are suitable alternatives. I did originally think about using the `Named` attribute, but I didn't want to use it because it then added a Ninject dependency to the classes that contain the attribute in their constructors. However, the IProvider solution does not suffer from this problem. The provider can remain in the same project as the Ninject modules and the bindings stay relatively clear of complexity. I will try the provider method and see if it simplifies my setup. – Robert Hargreaves Jan 07 '15 at 17:19
  • Unfortunately the Provider route didn't work. I made a CacheProvider with that logic, but when it came to inject it matched not only the CacheProvider but two named bindings too: More than one matching bindings are available. Matching bindings: 1) binding from ICache to method 2) binding from ICache to method 3) provider binding from ICache to ICache (via CacheProvider) Seems the naming doesn't stop the bindings from being injected by default. – Robert Hargreaves Jan 08 '15 at 20:39
  • I got it working by adding ".BindingConfiguration.IsImplicit = true" to both the named ICache bindings. Now it works as expected! I got the idea from here: http://stackoverflow.com/questions/5997483/ninject-default-contextual-binding – Robert Hargreaves Jan 08 '15 at 21:00
  • oh yes you're right. Thanks. I've forgotten that. This is an important difference between `.Named()` and `.When()` bindings. – BatteryBackupUnit Jan 09 '15 at 06:19