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 FileCodeParameter
s 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);
}
}
}