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):
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.
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();
}
}
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.
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);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With