c# - net - autofac xamarin
Pasando parámetros a constructores utilizando Autofac. (3)
Soy muy nuevo en Autofac, así que es posible que lo esté haciendo mal uso por completo.
Digamos que tengo una clase que tiene esta estructura:
public class HelperClass : IHelperClass
{
public HelperClass(string a, string b)
{
this.A = a;
this.B = b;
}
}
y tengo dos clases que usan esa clase, pero requieren valores predeterminados diferentes para el constructor. El segundo constructor es SOLO para propósitos de prueba, siempre querremos una clase de clase de ayuda en la aplicación "real":
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;
}
}
Aquí está mi módulo de AutoFac:
public class SomethingModule: Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<DoesSomething>().As<IDoesSomething>();
builder.RegisterType<DoesSomethingElse>().As<IDoesSomethingElse();
}
}
Mis preguntas):
- Cuando llamo a resolver en DoesSomething o DoesSomethignElse, ¿resolverá el constructor interno en lugar del público? ¿Necesito dejar IHelperClass no registrado?
- En caso afirmativo, ¿cómo hago que pase diferentes parámetros a cada instancia de IHelperClass dependiendo de si se usa en DoesSomething o DoesSomethingElse?
Autofac no utiliza constructores no públicos. Por defecto, solo encuentra públicos y simplemente no ve a los demás. A menos que use .FindConstructorsWith(BindingFlags.NonPublic)
, solo verá constructores públicos. Por lo tanto, su escenario debería funcionar como espera que lo haga.
Hay dos formas de pasar parámetros en Autofac:
Cuando esté registrando el componente :
Cuando registra componentes, tiene la capacidad de proporcionar un conjunto de parámetros que se pueden usar durante la resolución de servicios basados en ese componente. Autofac ofrece varias estrategias de coincidencia de parámetros diferentes:
-
NamedParameter
- hace coincidir los parámetros de destino por nombre -
TypedParameter
:TypedParameter
coincidir los parámetros de destino por tipo (se requiere una coincidencia de tipo exacta) Parámetro
ResolvedParameter
- coincidencia de parámetros flexible// 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
yTypedParameter
pueden suministrar valores constantes.ResolvedParameter
se puede utilizar como una forma de suministrar valores recuperados dinámicamente del contenedor, por ejemplo, resolviendo un servicio por nombre.
En caso de que quiera pasar como parámetro un servicio que ya está registrado, por ejemplo, IConfiguration
, puede resolver el parámetro como se muestra a continuación:
builder.RegisterType<Service>()
.As<Iervice>()
.WithParameter((pi, ctx) => pi.ParameterType == typeof(IConfiguration) && pi.Name == "configuration",
(pi, ctx) => ctx.Resolve<IConfiguration>());
Cuando esté resolviendo el componente :
Una forma de pasar el parámetro en tiempo de ejecución en Autofac es usar el método Resolve
. Podrías crear una clase como esta:
public class ContainerManager
{
public IContainer Container {get;set;}
//...
public T[] ResolveAllWithParameters<T>(IEnumerable<Parameter> parameters)
{
return Container.Resolve<IEnumerable<T>>(parameters).ToArray();
}
}
Parameter
es una clase abstracta que pertenece a Autofac, puede usar la clase NamedParameter
para pasar los parámetros que necesita. Puede usar la clase ContainerManager
como se muestra a continuación:
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 esta manera, puede pasar los parámetros en tiempo de ejecución utilizando un Dictionary<string, object>
cuando esté resolviendo un componente específico.
Usar un método de extensión podría ser aún más 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();
}
}
Siempre puede usar el método WithParameter
para especificar explícitamente un parámetro de constructor:
builder.RegisterType<DoesSomething>()
.As<IDoesSomething>()
.WithParameter("helper", new HelperClass("do", "something"));
builder.RegisterType<DoesSomethingElse>()
.As<IDoesSomethingElse>()
.WithParameter("helper", new HelperClass("do", "somethingelse"));
Por lo que puedo decir, no hay necesidad de una interfaz para HelperClass
porque esencialmente es solo un poseedor de valor.
Para que esto funcione, necesitarías hacer público al constructor interno, creo.