How do I use lambda to specify a constructor argument?
I'm not too familiar with lambdas and expression injection, but I've used this syntax many times in MVC where a lambda identifies a property for an object:
Html.Label(model => model.Foo)
In my application, I use Ninject conditional bindings to provide an instance of a class Settings
that is injected when I request an instance Class
. Mine Class
looks like this:
public class Class
{
private readonly Settings settings;
public Settings Settings { get { return settings; } }
public Class(Settings settings)
{
this.settings = settings;
}
}
I have some code that looks like this to get an instance Class
. I know this is a service locator antivirus program , but we have no choice in this case due to other restrictions:
var settings = new Settings();
var instance = Ioc.Instance.Get<Class>("settings", settings);
I would like to refactor it so that it is strongly typed using a lambda to indicate which argument in the constructor I supply:
var settings = new Settings();
var instance = Ioc.Instance.Get<Class>(x => x.settings, settings);
So, is this possible, and what does the code look like?
source to share
It is clear that there is no factory (factory) interface, so it should be introduced to avoid using the container directly.
The Ninject factory (factory) extension can be used to create an instance like this:
Declare a factory interface:
public interface IFactory
{
Class Create(Settings settings);
}
Add an anchor to the root of the composition:
kernel.Bind<IFactory>().ToFactory();
Use a factory to get an instance:
var settings = new Settings();
var factory = Ioc.Instance.Get<IFactory>();
var instance = factory.Create(settings);
Refer to ninject / ninject.extensions.factory for alternatives.
source to share
The problem with constructor argument names and expressions is that an expression is only valid / complete when it covers all constructor parameters. Now I assume you want to insert multiple parameters (have a ninject descriptor) and for one or two specific parameters you want to pass a value, let's say it looks like this:
public interface IFoo { }
public class Foo : IFoo
{
public Foo(IServiceOne one, IServiceTwo two, string parameter) {...}
}
Ninject supports ctor expressions, but only for bindings, and they work like this:
IBindingRoot.Bind<IFoo>().ToConstructor(x =>
new Foo(x.Inject<IServiceOne>(), x.Inject<IServiceTwo>(), "staticArgument");
so instead of specifying the "staticArgument" that interests you, you also need to specify IServiceOne
and IServiceTwo
. What if the constructor changes? Well, the call needs to be adapted! A lot of work for passing a simple simple parameter.
Now if you still want to do this, I suggest taking a look at the code ToConstructor
and creating a similar extension for the call Get
that will translate some call
IResolutionRoot.Get<IFoo>(x =>
new Foo(
x.Ignore<IServiceOne>(),
x.Ignore<IServiceTwo>(),
x.UseValue("mystring"));
to
IResolutionRoot.Get<IFoo>(new ConstructorArgument("parameter", "mystring"));
However, I would suggest referring to @Sergey Brunov and using Ninject.Extensions.Factory . Now I think you will say that this is not good because you still have to provide a parameter name which is not refactoring and has no problem (no code completion ...).
However, there is a solution to the problem: instead of using a constructor argument that "matches" the argument name, you can use a type match argument. Okay, there is a catch. If you have multiple arguments of the same type ... it won't work. But I think this is rarely the case, and you can still inject a container data class to address it:
public class FooArguments
{
string Argument1 { get; set; }
string Argument2 { get; set; }
}
Now, how can you use type matching? There are two ways:
- Use a
Func<string, IFoo>
factory. Just enterFunc<string, IFoo>
where you want to create andIFoo
. - Extend the Factory extension. Yes, you heard that right ;-) It's actually not that hard. You just need to implement a custom one
IInstanceProvider
(see also http://www.planetgeek.ch/2011/12/31/ninject-extensions-factory-introduction/ ) so you can do something like:
public interface IFooFactory
{
IFoo Create([MatchByType]string someParam, string matchByName);
}
(==> use the attribute to tell the Factory extension how to pass the parameter to the request Get<IFoo>
).
source to share
Have a look at the following article - http://handcraftsman.wordpress.com/2008/11/11/how-to-get-c-property-names-without-magic-strings/
In particular
public static class Extensions
{
public static string GetPropertyName<T,TReturn>(this Expression<Func<T,TReturn>> expression)
{
MemberExpression body = (MemberExpression)expression.Body;
return body.Member.Name;
}
}
Note. This method may not work for all possible uses of expressions to specify a property name for a class, and therefore may need to be leveraged based on your needs (as you usually need).
But essentially, once you have this helper method, your call becomes
var settings = new Settings();
Ioc.Instance.Get<Class>(GetPropertyName(x => x.settings), settings);
source to share