I have a component I would like consumers to be able to resolve either by directly referencing its type Component
, which would provide a default behavior, or as a factory with a single string parameter Func<string, Component>
, which would provide an alternate behavior based on the value specified.
This is easy if the component constructor looks and behaves just like what I want:
public class Component
{
// Single optional string parameter with desired behavior
public Component(string arg = "defaultValue") { ... }
}
// Register as self
builder.RegisterType<Component>().AsSelf()
// Consumer can resolve the component using the type itself
// or by using Func<string, T> and providing an argument
public class Consumer
{
public Consumer(Component c, Func<string, Component> cFactory)
{
var c2 = cFactory("customValue");
}
}
However, if the component constructor does not look like this (e.g. I need to add a bit of code to translate the resolution argument into the constructor argument(s)), I'm not sure how to support the same experience on the consumer side while using a lambda expression to register the type.
// Different constructor signature
public Component(SomeType t, int i) // different arguments
// Register using a lambda expression so I can translate the resolution argument
// to the actual constructor arguments
builder.Register((c, p) => new Component(...what?...));
// Same consumer usage
public Consumer(Component c, Func<string, Component> cFactory)
It's not clear in the docs how to accomplish this, since the example given uses p.Named<string>("parameterName")
, where "parameterName" matches the actual name of the component's constructor parameter. I need a way to add a bit of code that will map a single named resolution parameter to an arbitrary constructor signature.
Is this possible?
UPDATE:
Using the info in Cyril's answer, I arrived at an excellent solution using a delegate:
// Define a delegate with the resolution signature I want
delegate Component ComponentFactory(string arg);
// Register the component with default behavior, and the factory with the
// desired alternate behavior
builder.Register(c => new Component(...default args...));
builder.Register<ComponentFactory>(c => arg => new Component(...use arg here...)).AsSelf();
// Consumer can consume either the component or the factory
public Consumer(Component c, ComponentFactory cFactory)
{
var c2 = cFactory("customValue");
}
Warning: You can't use the c
argument inside the delegate implementation, because that will be called later by the consumer after the resolution operation has already completed. If you need to use the c
argument, e.g. to resolve some other dependency, use it outside the delegate implementation and then use the results as needed.
You can register your own Func<String, Component>
builder.Register<Func<String, Component>>(c =>
{
IComponentContext context = c.Resolve<IComponentContext>();
return (String s) => context.Resolve<Component>(TypedParameter.From(Int32.Parse(s)));
});
If you want to use the lambda registration you can do something like this :
builder.RegisterType<Component>().Named<Component>("internal");
builder.Register((c, p) =>
{
String s = p.TypedAs<String>();
Int32 i = Int32.Parse(s);
return c.ResolveNamed<Component>("internal", TypedParameter.From(i));
}).As<Component>();
You have to register Component
as named component first to avoid circular dependency loop.