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.
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:Note:
IInstanceProvider
will also make the argument available further "downstream" (it "inherits" the parameter)string
is very verbose so you may want to choose to wrap it in another type, likeclass ConnectionInfo
.Contextual Binding combined with parameter injection
So let's say we create our own
FileType
type to be more verbose than just usingstring
:(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 forWhen
-contextual bindings and also inject it's value based on type-matching (as above):Now let me create some sample codes so we can later verify that it works:
What's missing? The factory. We could use the
ToFactory
binding with customIInstanceProvider
but unless we're going to create lots of factories withFileCodeParameter
s i don't think it makes sense, so let's keep it simple:Now let's have it all come together:
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: