1

Having trouble figuring out how to manage contextual binding in the scenario where two classes have the same underlying interface dependency, but each class ctor's parameter is named differently. Pseudo code below to demonstrate my situation:

    interface IThing { }
    public class Thing1 : IThing { public Thing1(string fileCode) { } }
    public class Thing2 : IThing { public Thing2(string fileCode) { } }
    interface IThingFactory { IThing CreateThing(string fileCode); }

    interface IDependentThing { }
    public class A : IDependentThing { public A(string fileCode, IThingFactory thingFactory) { } }
    public class B : IDependentThing { public B(string fileCd, IThingFactory thingFactory) { } } //How to handle binding for this dependent?
    interface IDependentThingFactory { IDependentThing CreateDependentThing(string fileCode); }

    //...

    public override void Load()
    {
        Bind<IThing>().ToMethod(ctx =>
        {
            var fileCode = ctx.Parameters.First(p => p.Name == "fileCode").GetValue(ctx, null) as string;
            IThing thing = null;

            if (fileCode == "FileType1")
            {
                Bind<Thing1>().ToSelf().WithConstructorArgument("fileCode", fileCode);
                thing = Kernel.Get<Thing1>();
            }
            else if (fileCode == "FileType2")
            {
                Bind<Thing2>().ToSelf().WithConstructorArgument("fileCode", fileCode);
                thing = Kernel.Get<Thing2>();
            }
            return thing;
        });

        Bind<IThingFactory>().ToFactory();
        Bind<IDependentThingFactory>().ToFactory();
    }

//Later...
using (TextReader tr = new StreamReader(path))
{
    string firstLine = tr.ReadLine();

    if (firstLine.Substring(838, 1) == ".")
    {
        fileCode = "FileType1";
    }
    else if (firstLine.Substring(883, 1) == ".")
    {
        fileCode = "FileType2";
    }

    //won't work for creating B
    Kernel.Get<IDependentThing>(new ConstructorArgument("fileCode", fileCode));

    //or maybe...

    //seems to eliminate my problem by allowing me to handle variations
    //in parameter names  from within A and B's ctors, but looks like it
    //requires injecting factories along the chain (see A & B ctor arguments).
    dependentThingFactory.CreateDependentThing(fileCode) 
};

fileCode is computed based off of some analysis of local files. Once the type of file is determined, I want Ninject to hand back the appropriate object for processing that file

How would I handle the binding for B since the existing binding I defined requires a constructor parameter with a different name? Is there a better way to do this in general?

I guess I could just use p.Name == "fileCode" || p.Name == "fileCd", but I can't shake the feeling that I'm doing something wrong (feels messy). Also, I'm not thrilled about pulling parameters by name, and I've thought about maybe creating a custom type that would give Ninject something more concrete to match against versus a string parameter. From where I'm standing it looks like I either just manage the multiple parameter names situation, or switch to custom types as my parameters instead of strings.

Brett Rossier
  • 3,420
  • 3
  • 27
  • 36
  • Where do you get the value of the connection string from? Is this something "static"... or on the context of a request, or..? – BatteryBackupUnit Feb 28 '16 at 22:30
  • Well I really just used connectionString as an example. My actual code is using a string parameter, however. What I'm going for is perhaps a cleaner way of grabbing string parameters, but I'm just not sure how (my expectations are probably off). It seems problematic to me to have binding set up for A's "connectionString" ctor parameter, and then also have to account for B's ctor parameter named "connStr". Obviously if I added a C class into the graph, maybe it will have "cStr". So I have to keep changing the code to account for variations in parameter naming for the same conceptual thing. – Brett Rossier Feb 29 '16 at 00:11
  • Also I think I may have had this set up incorrectly based on a misunderstanding of how things worked with parameters that need to be passed to multiple dependencies in the chain. It looks like if I change A and B's ctors to accept IThingFactory instead of IThing, then the variations of parameter naming in A and B's ctors can be handled there in the ctors instead of in my binding code. By all means please correct me if I'm wrong, or suggest a better way, I'm still learning the ropes of Ninject. – Brett Rossier Feb 29 '16 at 00:18
  • But where's that string parameter (value) coming from? It's not coming out of thin air, is it? ;-) There's several technical "issues" which your question relates to: 1) passing parameters, 2) passing them further down the chain, 3) selecting a type based on contextual information. Now depending on where the parameter stems from and how long this information is valid for, we can entirely change the approach on how to perform 3), maybe influencing 1) and 2) or making 2) obsolete alltogether. – BatteryBackupUnit Feb 29 '16 at 08:23
  • Sorry for the confusion... although I myself am certainly confused ;) The string parameter I'm dealing with is being computed and then passed in to IDependentThing ctor's, or passed into a factory method for IDependentThing. This is all in a console app, and everything's transient scope (as I understand it). – Brett Rossier Feb 29 '16 at 15:01
  • can you please give a more complete example of the computation and also of the selection `connStr.Contains("bar"))` etc? It seems probable to me that you have code duplication/improper separation of concern issues: is the selection not based on something which was computed (or computed upon) by the "computer"? – BatteryBackupUnit Feb 29 '16 at 17:09
  • Updated the pseudo-code. The selection of the appropriate object in the binding code is in fact determined by the computed value (fileCode). – Brett Rossier Feb 29 '16 at 20:21

1 Answers1

1

Making parameter injection more refactor safe and making them available for the whole resolution context

Instead of "named parameters" you can use a "type matching" or "typed" parameter. The factories IInstanceProvider can be exchanged for another one which does so:

kernel.Bind<IThingFactory>()
      .ToFactory(() => new TypeMatchingArgumentInheritanceInstanceProvider());

Note:

  • this IInstanceProvider will also make the argument available further "downstream" (it "inherits" the parameter)
  • a string is very verbose so you may want to choose to wrap it in another type, like class ConnectionInfo.

Contextual Binding combined with parameter injection

So let's say we create our own FileType type to be more verbose than just using string:

public class FileCode
{
    public FileCode(string value)
    {
        Value = value;
    }

    public string Value { get; private set; }
}

(maybe you want to replace that with an enum?)

Since your requirements are more complex we're going to have to change things up a little bit. We're going to create our own IConstructorArgument to easily be able to match it for When-contextual bindings and also inject it's value based on type-matching (as above):

internal class FileCodeParameter : IConstructorArgument
{
    private readonly FileCode fileCode;

    public FileCodeParameter(FileCode fileCode)
    {
        this.fileCode = fileCode;
    }

    public string Name { get { return "File Code Parameter"; } }

    public bool ShouldInherit { get { return true; } }

    public FileCode FileCode  { get { return this.fileCode; } }

    public bool Equals(IParameter other)
    {
        var otherFileCodeParameter = other as FileCodeParameter;
        if (otherFileCodeParameter == null)
        {
            return false;
        }

        return otherFileCodeParameter.fileCode == this.fileCode;
    }

    public object GetValue(IContext context, ITarget target)
    {
        return this.fileCode;
    }

    public bool AppliesToTarget(IContext context, ITarget target)
    {
        return target.Type == typeof(FileCode);
    }
}

Now let me create some sample codes so we can later verify that it works:

public interface IThing
{
    FileCode FileCode { get; }
}

public abstract class Thing : IThing
{
    protected Thing(FileCode fileCode)
    {
        FileCode = fileCode;
    }

    public FileCode FileCode { get; private set; }
}

public class ThingFoo : Thing
{
    public ThingFoo(FileCode fileCode) : base(fileCode) { }
}

public class ThingBar : Thing
{
    public ThingBar(FileCode fileCode) : base(fileCode) { }
}

public interface IOtherThing
{
    FileCode FileCode { get; }
}

public abstract class OtherThing : IOtherThing
{
    protected OtherThing(FileCode fileCode)
    {
        FileCode = fileCode;
    }

    public FileCode FileCode { get; private set; }
}

public class OtherThingFoo : OtherThing
{
    public OtherThingFoo(FileCode fileCode) : base(fileCode) { }
}

public class OtherThingBar : OtherThing
{
    public OtherThingBar(FileCode fileCode) : base(fileCode) { }
}

public class OtherThingWrapper
{
    public OtherThingWrapper(IOtherThing otherThing)
    {
        OtherThing = otherThing;
    }

    public IOtherThing OtherThing { get; private set; }
}

public class FileProcessor
{
    public FileProcessor(IThing thing, OtherThingWrapper otherThingWrapper)
    {
        Thing = thing;
        OtherThingWrapper = otherThingWrapper;
    }

    public IThing Thing { get; private set; }
    public OtherThingWrapper OtherThingWrapper { get; private set; }
}

What's missing? The factory. We could use the ToFactory binding with custom IInstanceProvider but unless we're going to create lots of factories with FileCodeParameters i don't think it makes sense, so let's keep it simple:

public interface IFileProcessorFactory
{
    FileProcessor Create(FileCode fileCode);
}

internal class FileProcessorFactory : IFileProcessorFactory
{
    private readonly IResolutionRoot resolutionRoot;

    public FileProcessorFactory(IResolutionRoot resolutionRoot)
    {
        this.resolutionRoot = resolutionRoot;
    }

    public FileProcessor Create(FileCode fileCode)
    {
        return this.resolutionRoot.Get<FileProcessor>(new FileCodeParameter(fileCode));
    }
}

Now let's have it all come together:

public class Test
{
    [Fact]
    public void FactMethodName()
    {
        var fooFileCode = new FileCode("foo");
        var barFileCode = new FileCode("bar");

        var kernel = new StandardKernel();
        kernel
            .Bind<IFileProcessorFactory>()
            .To<FileProcessorFactory>();

        kernel
            .Bind<IThing>()
            .To<ThingFoo>()
            .WhenFileCode(fooFileCode);
        kernel
            .Bind<IThing>()
            .To<ThingBar>()
            .WhenFileCode(barFileCode);

        kernel
            .Bind<IOtherThing>()
            .To<OtherThingFoo>()
            .WhenFileCode(fooFileCode);
        kernel
            .Bind<IOtherThing>()
            .To<OtherThingBar>()
            .WhenFileCode(barFileCode);


        var fileProcessor = kernel.Get<IFileProcessorFactory>().Create(barFileCode);
        fileProcessor.Thing.Should().BeOfType<ThingBar>();
        fileProcessor.Thing.FileCode.Should().Be(barFileCode);
        fileProcessor.OtherThingWrapper.OtherThing.Should().BeOfType<OtherThingBar>();
        fileProcessor.OtherThingWrapper.OtherThing.FileCode.Should().Be(barFileCode);
    }
}

public static class BindingExtensionsForFileCodes
{
    public static IBindingInNamedWithOrOnSyntax<T> WhenFileCode<T>(
        this IBindingWhenSyntax<T> syntax,
        FileCode fileCode)
    {
        return syntax.When(req => req
            .Parameters
            .OfType<FileCodeParameter>()
            .Single()
            .FileCode.Value == fileCode.Value);
    }
}

That's it! - the FileCode is both being injected and being used for selection of implementation - as the parameter is "inherited", it also works deeper down the object tree.

Below, just for reference, all of the code for easier copy & paste:

using FluentAssertions;
using Ninject;
using Ninject.Activation;
using Ninject.Parameters;
using Ninject.Planning.Targets;
using Ninject.Syntax;
using System.Linq;
using Xunit;

namespace NinjectTest.ParameterContextual
{
    public class FileCode
    {
        public FileCode(string value)
        {
            Value = value;
        }

        public string Value { get; private set; }
    }

    public interface IThing
    {
        FileCode FileCode { get; }
    }

    public abstract class Thing : IThing
    {
        protected Thing(FileCode fileCode)
        {
            FileCode = fileCode;
        }

        public FileCode FileCode { get; private set; }
    }

    public class ThingFoo : Thing
    {
        public ThingFoo(FileCode fileCode) : base(fileCode) { }
    }

    public class ThingBar : Thing
    {
        public ThingBar(FileCode fileCode) : base(fileCode) { }
    }

    public interface IOtherThing
    {
        FileCode FileCode { get; }
    }

    public abstract class OtherThing : IOtherThing
    {
        protected OtherThing(FileCode fileCode)
        {
            FileCode = fileCode;
        }

        public FileCode FileCode { get; private set; }
    }

    public class OtherThingFoo : OtherThing
    {
        public OtherThingFoo(FileCode fileCode) : base(fileCode) { }
    }

    public class OtherThingBar : OtherThing
    {
        public OtherThingBar(FileCode fileCode) : base(fileCode) { }
    }

    public class OtherThingWrapper
    {
        public OtherThingWrapper(IOtherThing otherThing)
        {
            OtherThing = otherThing;
        }

        public IOtherThing OtherThing { get; private set; }
    }

    public class FileProcessor
    {
        public FileProcessor(IThing thing, OtherThingWrapper otherThingWrapper)
        {
            Thing = thing;
            OtherThingWrapper = otherThingWrapper;
        }

        public IThing Thing { get; private set; }
        public OtherThingWrapper OtherThingWrapper { get; private set; }
    }

    public interface IFileProcessorFactory
    {
        FileProcessor Create(FileCode fileCode);
    }

    internal class FileProcessorFactory : IFileProcessorFactory
    {
        private readonly IResolutionRoot resolutionRoot;

        public FileProcessorFactory(IResolutionRoot resolutionRoot)
        {
            this.resolutionRoot = resolutionRoot;
        }

        public FileProcessor Create(FileCode fileCode)
        {
            return this.resolutionRoot.Get<FileProcessor>(new FileCodeParameter(fileCode));
        }
    }

    public class Test
    {
        [Fact]
        public void FactMethodName()
        {
            var fooFileCode = new FileCode("foo");
            var barFileCode = new FileCode("bar");

            var kernel = new StandardKernel();
            kernel
                .Bind<IFileProcessorFactory>()
                .To<FileProcessorFactory>();

            kernel
                .Bind<IThing>()
                .To<ThingFoo>()
                .WhenFileCode(fooFileCode);
            kernel
                .Bind<IThing>()
                .To<ThingBar>()
                .WhenFileCode(barFileCode);

            kernel
                .Bind<IOtherThing>()
                .To<OtherThingFoo>()
                .WhenFileCode(fooFileCode);
            kernel
                .Bind<IOtherThing>()
                .To<OtherThingBar>()
                .WhenFileCode(barFileCode);


            var fileProcessor = kernel.Get<IFileProcessorFactory>().Create(barFileCode);
            fileProcessor.Thing.Should().BeOfType<ThingBar>();
            fileProcessor.Thing.FileCode.Should().Be(barFileCode);
            fileProcessor.OtherThingWrapper.OtherThing.Should().BeOfType<OtherThingBar>();
            fileProcessor.OtherThingWrapper.OtherThing.FileCode.Should().Be(barFileCode);
        }
    }

    internal class FileCodeParameter : IConstructorArgument
    {
        private readonly FileCode fileCode;

        public FileCodeParameter(FileCode fileCode)
        {
            this.fileCode = fileCode;
        }

        public string Name { get { return "File Code Parameter"; } }

        public bool ShouldInherit { get { return true; } }

        public FileCode FileCode  { get { return this.fileCode; } }

        public bool Equals(IParameter other)
        {
            var otherFileCodeParameter = other as FileCodeParameter;
            if (otherFileCodeParameter == null)
            {
                return false;
            }

            return otherFileCodeParameter.fileCode == this.fileCode;
        }

        public object GetValue(IContext context, ITarget target)
        {
            return this.fileCode;
        }

        public bool AppliesToTarget(IContext context, ITarget target)
        {
            return target.Type == typeof(FileCode);
        }
    }

    public static class BindingExtensionsForFileCodes
    {
        public static IBindingInNamedWithOrOnSyntax<T> WhenFileCode<T>(
            this IBindingWhenSyntax<T> syntax,
            FileCode fileCode)
        {
            return syntax.When(req => req
                .Parameters
                .OfType<FileCodeParameter>()
                .Single()
                .FileCode.Value == fileCode.Value);
        }
    }
}
BatteryBackupUnit
  • 12,934
  • 1
  • 42
  • 68
  • Ok, so what I'm seeing essentially is that it actually would be best to use a wrapper type to encapsulate the concept of a connectionString, and then use THAT as my ctor parameter, and set up my factory with `TypeMatchingArgumentInheritanceInstanceProvider` so that the param is automatically passed down the chain. I understand that correctly? – Brett Rossier Feb 29 '16 at 15:08
  • How would that change my binding code where the parameter is looked up? `ctx.Parameters.First(p => p.Name == "connectionString")` Does that go away? Also, I've matched on parameter types before by just doing `ctx.Parameters.First(p=>p.Type == typeof(WhateverType))`. I'm guessing that would be an inferior method? – Brett Rossier Feb 29 '16 at 15:11
  • @BrettRossier yes. Plus at this point my answer above is very incomplete which only relates to your first comment on your post. Since you provided some more details now i can extend the answer. – BatteryBackupUnit Feb 29 '16 at 17:06
  • i'll have to extend the answer tomorrow, gotta run now :/ – BatteryBackupUnit Feb 29 '16 at 17:46
  • @BrettRossier have a look whether the updated answer clear things up :-) – BatteryBackupUnit Mar 01 '16 at 12:40
  • Wow, thanks for going to the trouble of such a detailed answer. Most appreciated. This would be so much cleaner and simpler :) – Brett Rossier Mar 02 '16 at 04:40
  • Also, I'm guessing that if I need multiple parameters of the same underlying type, I could set up the same sort of code for something like `class FileCode1` and `class FileCode2`. Seems a bit overkill since it sort of duplicates named parameters in a hacky way, but it would definitely be nice not to have such involved binding code :\ Thanks again! – Brett Rossier Mar 02 '16 at 04:44
  • @BrettRossier depending on the issue, i guess that in most cases it's more appropriate to create a new type like `FileProcessingOptions` which contains two `FileCode`s, and use this for injection and selection. And / or make `FileCodeParameter` more generic, so it can be used for other things than just `FileCode`s. I grant though, that it's conceivable that you needed a combination of both (single-parameter and multi-parameter) in the same resolutin tree - that would be trickier. – BatteryBackupUnit Mar 03 '16 at 10:45