I have a BasePage class with two properties of the same dependency type and I couldn't find a way to provide the required parameters using Autofac
I tried to register the types and both BasePage properties are pointing now to the same last registered component which is FaxSender, I added a breakpoint at the function TryGetDeclaringProperty and it works fine and checking the names of properties of the BasePage class.
[UPDATED Example]
public class BasePage : System.Web.UI.Page
{
public ISender EmailSender { get; set; }
public ISender FaxSender { get; set; }
}
public class EmailSender : ISender
{
private readonly SmtpClient _smtpClient;
public EmailSender(SmtpClient smtpClient)
{
_smtpClient = smtpClient;
}
public void Send(INotification notification)
{
//...
}
}
public class FaxSender : ISender
{
private readonly SmtpClient _smtpClient;
public FaxSender(SmtpClient smtpClient)
{
_smtpClient = smtpClient;
}
public void Send(INotification notification)
{
//...
}
}
in Global.asax.cs
var emailSmtp = new SmtpClient
{
...
};
var emailSender = new EmailSender(emailSmtp);
var faxSmtp = new SmtpClient
{
...
};
var faxSender = new FaxSender(faxSmtp);
var builder = new ContainerBuilder();
//--------------------------------
builder.RegisterType<BasePage>()
.WithProperties(new Parameter[]{
new NamedPropertyParameter("EmailSender", emailSender),
new NamedPropertyParameter("FaxSender", faxSender),
});
//--------------------------------
//OR
//--------------------------------
builder.RegisterType<EmailSender>()
.Named<ISender>("email")
.WithParameter("smtpClient", emailSmtp);
builder.RegisterType<FaxSender>()
.Named<ISender>("fax")
.WithParameter("smtpClient", faxSmtp);
builder.RegisterType<BasePage>()
.AsSelf()
.WithProperties(new Parameter[] {
new ResolvedParameter(
(pi, c) => {
PropertyInfo ppi = null;
if (pi.TryGetDeclaringProperty(out ppi)) {
return ppi.Name == "EmailSender";
} else {
return false;
}
},
(pi, c) => c.ResolveNamed<ISender>("email")),
new ResolvedParameter(
(pi, c) => {
PropertyInfo ppi = null;
if (pi.TryGetDeclaringProperty(out ppi)) {
return ppi.Name == "FaxSender";
} else {
return false;
}
},
(pi, c) => c.ResolveNamed<ISender>("fax"))
});
//--------------------------------
//and then
var container = builder.Build();
_containerProvider = new ContainerProvider(container);
using (var scope = container.BeginLifetimeScope())
{
scope.Resolve<BasePage>();
}
in Default.aspx.cs
protected void Page_Load(object sender, EventArgs e)
{
//Object reference not set to an instance of an object. [Exception in both cases]
EmailSender.Send(new EmailNotification(...);
FaxSender.Send(new FaxNotification(...));
}
Due to the architecture of ASP.net webform, Page
instances are not created by Autofac you can't configure any registration for the page instance. The WithProperty
method won't be used in this case.
In your case one solution would be to manually resolve the dependency.
In your BasePage
component
public ISender EmailSender
{
get
{
var cpa = (IContainerProviderAccessor)this.Context.ApplicationInstance;
return cpa.ContainerProvider.RequestLifetime.ResolveNamed<ISender>("email");
}
}
Another solution would be to use IIndex<TKey, TValue>
. Instead of having 2 properties you can have only one which contains all your ISender
.
public IIndex<String, ISender> Senders {get; set; }
And when you need a specific sender you can access it through
ISender emailSender = this.Senders["email"];
By the way, if you want the expected behavior but for an instance created by Autofac you should use NamedPropertyParameter
instead of NamedParameter
.
// not working for webform !!!
builder.RegisterType<BasePage>()
.WithProperties(new Parameter[]{
new NamedPropertyParameter("X", emailSender),
new NamedPropertyParameter("Y", faxSender),
});
If you want to get your values with dependency injection there is no easy way to do it.
Autofac rely internally to this extension method :
public static class ParameterInfoExtensions
{
public static bool TryGetDeclaringProperty(this ParameterInfo pi, out PropertyInfo prop)
{
MethodInfo mi = pi.Member as MethodInfo;
if (mi != (MethodInfo)null && mi.IsSpecialName
&& mi.Name.StartsWith("set_", StringComparison.Ordinal)
&& mi.DeclaringType != (Type)null)
{
prop = mi.DeclaringType.GetTypeInfo().GetDeclaredProperty(mi.Name.Substring(4));
return true;
}
prop = null;
return false;
}
}
and you can use it with a ResolvedParameter
this way :
builder.RegisterType<EmailSender>()
.Named<ISender>("email")
.WithParameter("smtpClient", emailSmtp);
builder.RegisterType<FaxSender>()
.Named<ISender>("fax")
.WithParameter("smtpClient", faxSmtp);
builder.RegisterType<BasePage>()
.AsSelf()
.WithProperties(new Parameter[] {
new ResolvedParameter(
(pi, c) => {
PropertyInfo ppi = null;
if (pi.TryGetDeclaringProperty(out ppi)) {
return ppi.Name == "SmtpClient";
} else {
return false;
}
},
(pi, c) => c.ResolveNamed<ISender>("email")),
new ResolvedParameter(
(pi, c) => {
PropertyInfo ppi = null;
if (pi.TryGetDeclaringProperty(out ppi)) {
return ppi.Name == "FaxClient";
} else {
return false;
}
},
(pi, c) => c.ResolveNamed<ISender>("fax"))
});