0

I have two methods to send emails. One makes the UI stop while it sends the email, and the other one is supposed to be asynchronous, keeping the UI active (and in my use case, redirecting to another page later on) while the email is send in the background, on another thread. I am not too experienced with asynchronous functions, but I guess this was supposed to work.

My specific problem is:

This method sends the email

    public static void sendEmail(string nameFrom, string passwordFrom, string to, string subject, string text, string smtpHost, string attachments = null, int port = 25)
    {            
        System.Net.Mail.MailMessage message = new System.Net.Mail.MailMessage();
        System.Net.NetworkCredential credentials = new System.Net.NetworkCredential(nameFrom, passwordFrom);
        System.Net.Mail.MailAddress from = new System.Net.Mail.MailAddress(nameFrom);
        System.Net.Mail.SmtpClient smtp = new System.Net.Mail.SmtpClient();

        message.To.Add(to);
        message.From = from;
        message.Subject = subject;
        message.Body = text;
        message.IsBodyHtml = true;

        smtp.Host = smtpHost; 
        smtp.Port = port;
        smtp.EnableSsl = true;
        smtp.DeliveryMethod = System.Net.Mail.SmtpDeliveryMethod.Network;
        smtp.UseDefaultCredentials = false;
        smtp.Credentials = credentials;

        if (attachments != null && attachments != "")
        {
            if (System.IO.File.Exists(attachments) == true)
            {
                System.Net.Mail.Attachment attFile = new System.Net.Mail.Attachment(attachments);
                message.Attachments.Add(attFile);
            }
        }

        smtp.Send(message);
    }

This one doesn't

 public static async Task sendEmailAsync(string nameFrom, string passwordFrom, string to, string subject, string text, string smtpHost, string attachments = null, int port = 25)
    {              
        System.Net.Mail.MailMessage message = new System.Net.Mail.MailMessage();
        System.Net.NetworkCredential credentials = new System.Net.NetworkCredential(nameFrom, passwordFrom);
        System.Net.Mail.MailAddress from = new System.Net.Mail.MailAddress(nameFrom);
        System.Net.Mail.SmtpClient smtp = new System.Net.Mail.SmtpClient();

        message.To.Add(to);
        message.From = from;
        message.Subject = subject;
        message.Body = text;
        message.IsBodyHtml = true;

        smtp.Host = smtpHost; 
        smtp.Port = port;
        smtp.EnableSsl = true;
        smtp.DeliveryMethod = System.Net.Mail.SmtpDeliveryMethod.Network;
        smtp.UseDefaultCredentials = false;
        smtp.Credentials = credentials;

        if (attachments != null && attachments != "")
        {
            if (System.IO.File.Exists(attachments) == true)
            {
                System.Net.Mail.Attachment attFile = new System.Net.Mail.Attachment(attachments);
                message.Attachments.Add(attFile);
            }
        }                    
        await smtp.SendMailAsync(message);                    
    }

Why is the second one not sending the email and how can I fix it?

mason
  • 31,774
  • 10
  • 77
  • 121
Tiago
  • 347
  • 1
  • 3
  • 12
  • 1
    What is the calling code for your async method? Something somewhere in the chain is likely causing a termination before this is ran – maccettura Oct 11 '18 at 16:45
  • @maccettura i'm calling the method on a buttonOnclick with `Generic.sendEmailAsync("iwilljusthidetheemail@gmail.com", "andthepasswordaswell", new_user.email, "Maybe the message too", "smtp.gmail.com", null, 587);` and even without any code being ran after this, the email does not send – Tiago Oct 11 '18 at 16:53
  • 2
    If you are calling it from an event handler you might want to use [SendAsync()](https://learn.microsoft.com/en-us/dotnet/api/system.net.mail.smtpclient.sendasync?view=netframework-4.7.2) instead. This is a `void` method (instead of returning a task) but this one does not block the calling thread like `Send()` does. If you want to use the `SendMailAsync()` method (that returns a task) you need to mark your button click event handler with `async` and then `await` the call to `sendEmailAsync()` – maccettura Oct 11 '18 at 16:57
  • @maccettura my problem with SendAsync() is that [After calling SendAsync, you must wait for the e-mail transmission to complete before attempting to send another e-mail message using Send or SendAsync.](https://codereview.stackexchange.com/a/150768), it is unable to send multiple emails and that may be a problem, I guess, if multiple users try to do this same task at once (i'm not sure this will be a problem) – Tiago Oct 11 '18 at 17:28
  • 1
    Ok, so just add `async` to your event handler, then `await` the call to `sendEmailAsync()`. Like this `await Generic.sendEmailAsync("", "", "", "", "", null, 587);` – maccettura Oct 11 '18 at 17:37
  • @maccettura throws `System.InvalidOperationException: An asynchronous operation can not be started at this time. Asynchronous operations can only be started on an asynchronous module or processor or during certain events in the Page lifecycle. If this exception occurred while running a Page, make sure that the Page is marked as <% @ Page Async = "true"%>. This exception may also indicate an attempt to call an "async void" method, which is not generally supported in ASP.NET request processing. Alternatively, the asynchronous method should return a Task and the caller should wait for it.` – Tiago Oct 11 '18 at 17:46
  • @maccettura and upon changing the calling function to `protected async Task btn_Click(object sender, EventArgs e)` the asp page does not find the OnClick method, because it's not void – Tiago Oct 11 '18 at 17:50
  • 2
    You can use `async void` for event handlers (it's one of the few exceptions). But you need to mark the page as async, as explained in the error message. – mason Oct 11 '18 at 17:54
  • 1
    You stated you want to keep the UI active....in ASP.NET, if you await the result of an async call, then that's going to hold the response up. Sounds like you might want QueueBackgroundWorkItem, which is one of the techniques for running code in the background that Scott Hanselman [describes on his blog](https://www.hanselman.com/blog/HowToRunBackgroundTasksInASPNET.aspx). – mason Oct 11 '18 at 17:56
  • @mason So, after turning setting the page Async to true, it solved the problem and now everything runs right, much thanks! But as you are stating now, I belive the UI is being held and the page is not being redirected until the email is sent, so, it comes down to the same as using SendMail(). What would be then the most efficient solution to this? (I want the user to be redirected even if the email has not been sent yet) – Tiago Oct 11 '18 at 18:17
  • I posted several solutions in my last comment - did you read about them? – mason Oct 11 '18 at 18:36
  • 1
    Your looking for a fire and forget Task scenario. – mxmissile Oct 11 '18 at 18:41
  • You are not getting anything in return, so why use task? you can just use async void sendEmailAsync(string nameFrom, string passwordFrom, string to, string subject, string text, string smtpHost, string attachments = null, int port = 25). One more thing: You are using certificates (smtp.EnableSsl = true). Are you Sure that there are no errors thrown in the method? You need to wrap it into try/catch block and throw exception when cough, then where you are calling this async function also do try/catch and in case of error examine inner exception – Yuri Oct 11 '18 at 18:58

1 Answers1

-2

I would recommend using Backgroundworker in sending an email in a different thread. I think this post is excellent in explaining. Async/await vs BackgroundWorker I would have put this in the comments but not enough street cred here.

John112358
  • 98
  • 10
  • `async/await` are meant for IO bound operations. OP does not need, nor want a background worker for an IO bound operation... – maccettura Oct 11 '18 at 16:50