0

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(...));
}
Jason Aller
  • 3,541
  • 28
  • 38
  • 38
Rasheed
  • 47
  • 5
  • Have you added the `PropertyInjectionModule` ? as stated in the documentation here : https://autofaccn.readthedocs.io/en/latest/integration/webforms.html – Cyril Durand Jun 06 '19 at 23:25
  • yes I did but couldn't add the section under httpModules becuase I was getting HTTP Error 500.22 – Rasheed Jun 07 '19 at 08:55
  • https://stackoverflow.com/questions/7370513/http-error-500-22-internal-server-error-an-asp-net-setting-has-been-detected – Cyril Durand Jun 07 '19 at 09:35
  • I made the changes pointed out there and what's happening is that both BasePage properties are referencing the same and last registered component which is FaxSender. would trying Explicit Injection via Attributes be a better option here? thank you for your help @CyrilDurand – Rasheed Jun 07 '19 at 10:19
  • See my updated answer – Cyril Durand Jun 07 '19 at 12:01
  • @CyrilDurand in the case of using IIndex Senders how can I register these references? – Rasheed Jun 07 '19 at 12:33

1 Answers1

0

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"))
       });
Cyril Durand
  • 15,834
  • 5
  • 54
  • 62
  • thank you for your help. I've tried the first option with NamedPropertyParameter and it didn't work. and I tried the second option and I got a compile time error on the method TryGetDeclaringProperty and I couldn't figure out how to use it. – Rasheed Jun 06 '19 at 12:08
  • do you have any error with first solution ? The second solution uses [extensions methods](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods) you should wrap the static method within a static class – Cyril Durand Jun 06 '19 at 13:24
  • using the first solution, both ISender properties are being set as EmailSender type. I'll wrap the function in a static class, thanks – Rasheed Jun 06 '19 at 13:28
  • I tried the second solution and unfortunately it's not working, this time none of the properties of the BasePage were assigned, so I'm getting a NullReferenceException !! – Rasheed Jun 06 '19 at 14:53
  • I try both solution on a basic project and both work. Could you add breakpoint on the delegate to see whats happen – Cyril Durand Jun 06 '19 at 15:12
  • I've updated the example because I'm still stuck at it and still getting NullReferenceException when I try to access one of the properties of the BasePage class. – Rasheed Jun 06 '19 at 18:14
  • both solutions work! thank you a lot for your time. @CyrilDurand – Rasheed Jun 07 '19 at 12:48
  • how would changing the ISender interface to a generic one like ISender change the registration? should I post a new question? ISender where T : INotification – Rasheed Jun 13 '19 at 10:12
  • It would be easier if you could create a new question – Cyril Durand Jun 13 '19 at 14:58
  • https://stackoverflow.com/questions/56583975/how-to-resolve-multiple-dependencies-of-the-same-type-in-a-system-web-ui-page-wh – Rasheed Jun 13 '19 at 15:33