web-dev-qa-db-fra.com

Passer des paramètres aux constructeurs à l'aide d'Autofac

Je suis très nouveau dans l'autofac, il est donc possible que j'en fasse un mauvais usage.

Disons que j'ai une classe qui a cette structure:

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

et j'ai deux classes qui utilisent cette classe, mais nécessitent des valeurs par défaut différentes pour le constructeur. Le deuxième constructeur est JUST à des fins de test - nous voudrons toujours une HelperClass dans la "vraie" application:

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;
     }
}

Voici mon module AutoFac:

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

Mes questions):

  1. Lorsque j'appelle résoudre sur DoesSomething ou DoesSomethignElse - résoudra-t-il le constructeur interne au lieu du constructeur public? Dois-je laisser IHelperClass non enregistré?
  2. Si oui, comment puis-je lui faire passer des paramètres différents pour chaque instance de IHelperClass selon qu'elle est utilisée dans DoesSomething ou DoesSomethingElse?
37
Paul

Autofac n'utilise pas de constructeurs non publics. Par défaut, il n'en trouve que des publics et ne voit tout simplement pas les autres. À moins que vous n'utilisiez .FindConstructorsWith(BindingFlags.NonPublic), il ne verra que les constructeurs publics. Par conséquent, votre scénario devrait fonctionner comme prévu.

9
Pavel Gatilov

Vous pouvez toujours utiliser la méthode WithParameter pour spécifier explicitement un paramètre constructeur:

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

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

Pour autant que je sache, il n'y a pas besoin d'une interface pour HelperClass car il s'agit essentiellement d'un détenteur de valeur.

Pour que cela fonctionne, vous devez rendre public le constructeur interne, je pense.

55
Daniel Hilgarth

Il existe deux façons de transmettre des paramètres dans Autofac:

Lorsque vous enregistrez le composant :

Lorsque vous enregistrez des composants, vous avez la possibilité de fournir un ensemble de paramètres pouvant être utilisés lors de la résolution des services basés sur ce composant. Autofac propose plusieurs stratégies différentes de mise en correspondance des paramètres:

  • NamedParameter - fait correspondre les paramètres cibles par leur nom
  • TypedParameter - correspond aux paramètres cibles par type (correspondance exacte de type requise)
  • ResolvedParameter - mise en correspondance flexible des paramètres

    // 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 et TypedParameter ne peuvent fournir que des valeurs constantes.

    ResolvedParameter peut être utilisé comme un moyen de fournir des valeurs récupérées dynamiquement du conteneur, par ex. en résolvant un service par son nom.

Dans le cas où vous souhaitez passer en paramètre un service déjà enregistré, par exemple IConfiguration, vous pouvez résoudre le paramètre comme je le montre ci-dessous:

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

Lorsque vous résolvez le composant :

Une façon de passer un paramètre lors de l'exécution dans Autofac est d'utiliser la méthode Resolve. Vous pouvez créer une classe comme celle-ci:

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

Parameter est une classe abstraite qui appartient à Autofac, vous pouvez utiliser la classe NamedParameter pour passer les paramètres dont vous avez besoin. Vous pouvez utiliser la classe ContainerManager comme je le montre ci-dessous:

    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);
    }

De cette façon, vous pouvez passer les paramètres lors de l'exécution à l'aide d'un Dictionary<string, object> lorsque vous résolvez un composant spécifique.

L'utilisation d'une méthode d'extension pourrait être encore plus 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();
    }
}
44
octavioccl