0

I need to send mail in Async way. I have figured out to use Razor Generator to generate Html template from razor view. now i need to use SmtpClient.SendMailAsync to send the html as Mail. but i found Razor generator takes quite some time and i do not want to include the template generation part inside my send mail method as the send mail method should not be concerned about getting the Html Template.

I have sample code:

public static void SendEmailAsync<TModel>(TModel model, string templatePath, string subj, string toEmail, string cc = null, string bcc = null)
    {

        string templateFilePath = HostingEnvironment.MapPath(templatePath);
        // Generate the email body from the template file.
        // 'templateFilePath' should contain the absolute path of your template file.
        if (templateFilePath != null)
        {
            Task.Run(() =>
            {
                var emailHtmlBody = Engine.Razor.RunCompile(File.ReadAllText(templateFilePath),
                templateFilePath, model.GetType(), model);
                SendEmailAsync(subj, emailHtmlBody, toEmail, cc, bcc);
            });
        }
        else
        {
            throw new System.Exception("Could not find mail template.");
        }
    }

and the signature for SendMailAsync Is:

static async Task SendEmailAsync(string subj, string message, string toEmail, string cc = null, string bcc = null)
    {
        //Reading sender Email credential from web.config file  
        string fromEmail = ConfigurationManager.AppSettings["FromEmail"].ToString();
        string fromName = ConfigurationManager.AppSettings["FromName"].ToString();

        //creating the object of MailMessage  
        MailMessage mailMessage = new MailMessage();
        mailMessage.From = new MailAddress(fromEmail, fromName); //From Email Id  
        mailMessage.Subject = subj; //Subject of Email  
        mailMessage.Body = message; //body or message of Email  
        mailMessage.IsBodyHtml = true;

        string[] toMuliId = toEmail.Split(',');
        foreach (string toEMailId in toMuliId)
        {
            mailMessage.To.Add(new MailAddress(toEMailId)); //adding multiple TO Email Id  
        }


        if (cc != null)
        {
            string[] ccId = cc.Split(',');

            foreach (string ccEmail in ccId)
            {
                mailMessage.CC.Add(new MailAddress(ccEmail)); //Adding Multiple CC email Id  
            }
        }

        if (bcc != null)
        {
            string[] bccid = bcc.Split(',');

            foreach (string bccEmailId in bccid)
            {
                mailMessage.Bcc.Add(new MailAddress(bccEmailId)); //Adding Multiple BCC email Id  
            }
        }

        SmtpClient smtp = new SmtpClient
        {
            EnableSsl = true,
            Credentials = new NetworkCredential("", "")
        };

        //network and security related credentials  
        await smtp.SendMailAsync(mailMessage); //sending Email  
    }

No exceptions are thrown but i get the error:

System.InvalidOperationException: An asynchronous operation cannot be started at this time. Asynchronous operations may only be started within an asynchronous handler or module or during certain events in the Page lifecycle. If this exception occurred while executing a Page, ensure that the Page is marked <%@ Page Async="true" %>. This exception may also indicate an attempt to call an "async void" method, which is generally unsupported within ASP.NET request processing. Instead, the asynchronous method should return a Task, and the caller should await it.

  • The basic issue appears to be that your `static void SendEmailAsync(...)` method should be `static async Task SendEmailAsync(...)` instead. With that, you can `await` the `Task.Run()`. This is exactly the same issue as discussed in a number of previous asked questions, including the marked duplicate. – Peter Duniho Aug 22 '16 at 06:11
  • @PeterDuniho I just do not want to keep adding async keyword to all my methods up to the controller. – Mahmoud El Fishawy Aug 22 '16 at 06:19
  • @Aron Yes thanks i think it is the same issue. – Mahmoud El Fishawy Aug 22 '16 at 06:19
  • 1
    _"I just do not want to keep adding async keyword to all my methods up to the controller"_ -- you can't use `await` properly without doing that. So how did the answer you accepted solve your problem? – Peter Duniho Aug 22 '16 at 06:21
  • @PeterDuniho I just need this one method send mail to be in background operation i only need this one method. i just do not want to put async everywhere. so using Task.run i do not have to put the async everywhere i call my method. – Mahmoud El Fishawy Aug 22 '16 at 06:28

2 Answers2

1

Use this:

await Task.Run(async () =>
{
    await DoAsyncMethodAsync();
});
Catalin
  • 11,503
  • 19
  • 74
  • 147
  • Alternatively you can just return the `DoAsyncMethodAsync()`. – Aron Aug 22 '16 at 06:08
  • @Aron, do you mean `await Task.Run(DoAsyncMethodAsync())`? – Mike Henry Feb 02 '17 at 18:29
  • @MikeHenry no I do not. I think you mean `await Task.Run(DoMethodAsync)`. – Aron Feb 02 '17 at 18:35
  • @Aron, ok, thanks. I intended to write `await Task.Run(() => DoAsyncMethodAsync())`. I didn't think about calling Task.Run like you suggested: `await Task.Run(DoMethodAsync)` – Mike Henry Feb 02 '17 at 20:25
  • Why would you ever do this? Why not just call `await DoAsyncMethodAsync()` by itself? – TomDane Apr 01 '19 at 18:24
  • @TomDane if you call directly `await DoAsync()`, the current thread will wait for the execution of the async task. Using `Task.Run()`, you create a new thread which will execute in the background, and the current thread will not wait for it anymore. – Catalin Apr 03 '19 at 08:27
  • @Catalin I must be confused. You are right that `Task.Run()` will create a new thread. But I thought `await DoAsync()` doesn't block the current thread. In fact, I thought it released the thread, and when the async call returns, only then does the thread resume execution from the `await`. That is, `await DoAsync()` does not cause the thread to wait. – TomDane Apr 03 '19 at 14:20
  • 1
    @TomDane `await` does not block the current thread - this is correct. However, the main function will complete its execution only after the inside async method gets executed. Opening a new Thread will allow the main function to complete before the async call finishes (just as a fire-and-forget pattern). – Catalin Apr 03 '19 at 14:24
0

This issue is that you are running the following method every time an email is sent (this generates the initial class which takes time)

Engine.Razor.RunCompile

Ideally, you should be calling the following method and only then, if that throws an error, then calling RunCompile

Engine.Razor.Run

See this article on using a Template Manager with Caching

timkly
  • 793
  • 6
  • 14
  • 1
    Ideally you precompile the Razor at compilation...>_ – Aron Aug 22 '16 at 06:02
  • I've always lazy loaded the templates as needed plus I run a file watcher against the template directory which invalidates the template cache as needed so updates to templates are reflected in real time without the need to worry about recompiling the project :) – timkly Aug 22 '16 at 06:05