问题
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):
- 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?
- 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?
回答1:
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.
回答2:
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.
回答3:
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 nameTypedParameter
- 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
andTypedParameter
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();
}
}
来源:https://stackoverflow.com/questions/9066200/passing-parameters-to-constructors-using-autofac