Passing parameters to constructors using Autofac

C#Autofac

C# Problem Overview


I'm very new to autofac so it's possible that I'm completely misusing it.

Let's say I have a class that has this structure:

public class HelperClass : IHelperClass
{
     public HelperClass(string a, string b)
     {
         this.A = a;
         this.B = b;
     }
}

and I have two classes that use that class, but require different defaults for the constructor. The second constructor is JUST for testing purposes -- we will always want a HelperClass in the "real" app.:

public class DoesSomething: IDoesSomething
{
     public DoesSomething()
         : this(new HelperClass("do", "something"));
     {

     }

     internal DoesSomething(IHelperClass helper)
     {
          this.Helper = helper;
     }
}

public class DoesSomethingElse : IDoesSomethingElse
{
     public DoesSomethingElse()
         : this(new HelperClass("does", "somethingelse"));
     {

     }

     internal DoesSomethingElse(IHelperClass helper)
     {
          this.Helper = helper;
     }
}

Here's my AutoFac module:

public class SomethingModule: Module
{
    protected override void Load(ContainerBuilder builder)
    {
         builder.RegisterType<DoesSomething>().As<IDoesSomething>();
         builder.RegisterType<DoesSomethingElse>().As<IDoesSomethingElse();
    }
}

My question(s):

  1. When I call resolve on DoesSomething or DoesSomethignElse -- will it resolve the internal constructor instead of the public one? Do I need to leave IHelperClass unregistered?

  2. If yes, how do I make it pass different parameters to each instance of IHelperClass depending on whether it's used in DoesSomething or DoesSomethingElse?

C# Solutions


Solution 1 - C#

You can always use the WithParameter method to explicitly specify a constructor parameter:

builder.RegisterType<DoesSomething>()
       .As<IDoesSomething>()
       .WithParameter("helper", new HelperClass("do", "something"));

builder.RegisterType<DoesSomethingElse>()
       .As<IDoesSomethingElse>()
       .WithParameter("helper", new HelperClass("do", "somethingelse"));

As far as I can tell there is no need for an interface for HelperClass because it essentially is just a value holder.

For this to work you would need to make the internal constructor public, I think.

Solution 2 - C#

There are two ways to pass parameters in Autofac:

When you are registering the component:

When you register components, you have the ability to provide a set of parameters that can be used during the resolution of services based on that component. Autofac offers several different parameter matching strategies:

  • NamedParameter - match target parameters by name

  • TypedParameter - match target parameters by type (exact type match required)

  • ResolvedParameter - flexible parameter matching

     // Using a NAMED parameter:
     builder.RegisterType<ConfigReader>()
        .As<IConfigReader>()
        .WithParameter("configSectionName", "sectionName");// parameter name, parameter value. It's the same of this: new NamedParameter("configSectionName", "sectionName")
    
     // Using a TYPED parameter:
     builder.RegisterType<ConfigReader>()
        .As<IConfigReader>()
        .WithParameter(new TypedParameter(typeof(string), "sectionName"));
    
     // Using a RESOLVED parameter:
     builder.RegisterType<ConfigReader>()
        .As<IConfigReader>()
        .WithParameter(
          new ResolvedParameter(
            (pi, ctx) => pi.ParameterType == typeof(string) && pi.Name == "configSectionName",
            (pi, ctx) => "sectionName"));
    

    NamedParameter and TypedParameter can supply constant values only.

ResolvedParameter can be used as a way to supply values dynamically retrieved from the container, e.g. by resolving a service by name.

In case you want to pass as parameter a service that is already registered, eg, IConfiguration, you can resolve the parameter as I show below:

    builder.RegisterType<Service>()
           .As<Iervice>()
           .WithParameter((pi, ctx) => pi.ParameterType == typeof(IConfiguration) && pi.Name == "configuration",
                          (pi, ctx) => ctx.Resolve<IConfiguration>());

When you are resolving the component:

One way to pass parameter at runtime in Autofac is using the Resolve method. You could create a class like this:

public class ContainerManager
{
  public IContainer Container {get;set;}
  //...
  public T[] ResolveAllWithParameters<T>(IEnumerable<Parameter> parameters)
  {
    return Container.Resolve<IEnumerable<T>>(parameters).ToArray();
  }
}

Parameter is an abstract class that belongs to Autofac, you can use the NamedParameter class to pass the parameters that you need. You can use the ContainerManager class as I show below:

    public T[] ResolveAllWithParameters<T>(IDictionary<string,object> parameters )
    {
        var _parameters=new List<Parameter>();
        foreach (var parameter in parameters)
        {
            _parameters.Add( new NamedParameter(parameter.Key, parameter.Value));
        }
        return ContainerManager.ResolveAllWithParameters<T>(_parameters);
    }

This way you can pass the parameters at runtime using a Dictionary<string, object> when you are resolving an specific component.

Using an Extension Method could be even more simple:

public static class ContainerExtensions
{
    public static T[] ResolveAllWithParameters<T>(this IContainer Container, IDictionary<string, object> parameters)
    {
        var _parameters = new List<Parameter>();
        foreach (var parameter in parameters)
        {
            _parameters.Add(new NamedParameter(parameter.Key, parameter.Value));
        }
        return Container.Resolve<IEnumerable<T>>(_parameters).ToArray();
    }
}

Solution 3 - C#

Autofac does not use non-public constructors. By default, it only finds public ones and simply doesn't see the others. Unless you use .FindConstructorsWith(BindingFlags.NonPublic), it will see only public constructors. Therefore your scenario should work as you expect it to do.

Solution 4 - C#

Yes, it is possible to pass only a sub set of parameters:

public Contract(IPerson person, String name)
{ 
    this.Person = person;
    person.Name = name;
}

....

// this uses the person/name ctor. Person is factored and injected by the contianer
List<Parameter> parameters = new List<Parameter>();
parameters.Add(new NamedParameter("name", "cloe"));
contract = scope.Resolve<IContract>(parameters);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPaulView Question on Stackoverflow
Solution 1 - C#Daniel HilgarthView Answer on Stackoverflow
Solution 2 - C#octaviocclView Answer on Stackoverflow
Solution 3 - C#Pavel GatilovView Answer on Stackoverflow
Solution 4 - C#Roland RoosView Answer on Stackoverflow