Search code examples
c#dependency-injectionautofac

How do I expose arbitrary resolution parameters for an Autofac component registered using a lambda?


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.


Solution

  • 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.