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):
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.
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.
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 nomTypedParameter
- 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();
}
}