1

I've been working on a website where you can directly send an inquire mail to a specific email.

At first I thought that the mail was not sending because the site is just keep on loading after I click the send button. But when I check my email, I was surprise that the mail that I send was there. But when I checked my website(where I have the inquire form) it still loading.

The ViewBag that suppose to says "Your message has been sent!" still doesn't show, even though I already receive the mail. Seems like the

await smtp.SendMailAsync(message);

don't return. I'm new with this kind of thing. I hope someone could help me. Thank you in advance. Here is my controller:

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Index(EmailFormModel model, IEnumerable<HttpPostedFileBase> files)
    {
       try
       {

        if (ModelState.IsValid)
        {
            List<string> paths = new List<string>();

            foreach (var file in files)
            {
                if (file.ContentLength > 0)
                {
                    var fileName = Path.GetFileName(file.FileName);
                    var path = Path.Combine(System.Web.HttpContext.Current.Server.MapPath("~/App_Data/uploads"), fileName);
                    file.SaveAs(path);
                    paths.Add(path);
                }
            }

            var message = new MailMessage();
            foreach (var path in paths)
            {
                var fileInfo = new FileInfo(path);
                var memoryStream = new MemoryStream();
                using (var stream = fileInfo.OpenRead())
                {
                    stream.CopyTo(memoryStream);
                }
                memoryStream.Position = 0;
                string fileName = fileInfo.Name;
                message.Attachments.Add(new Attachment(memoryStream, fileName));
            }

            //Rest of business logic here
            string EncodedResponse = Request.Form["g-Recaptcha-Response"];
            bool IsCaptchaValid = (ReCaptcha.Validate(EncodedResponse) == "True" ? true : false);
            if (IsCaptchaValid)
            {

                var body = "<p><b>Email From:</b> {0} ({1})</p><p><b>Subject:</b> {2} </p><p><b>Software Description:</b></p><p>{4}</p><p><b>Message:</b></p><p>{3}</p>";
                message.To.Add(new MailAddress("email@mydomain.com"));  // replace with valid value 
                message.From = new MailAddress("email@randomdomain.com");  // replace with valid value
                message.Subject = "(Inquire for SELLING)";
                message.Body = string.Format(body, model.FromName, model.FromEmail, model.FromSubject, model.Message, model.Desc);
                message.IsBodyHtml = true;
                using (var smtp = new SmtpClient())
                {
                    var credential = new NetworkCredential
                    {
                        UserName = "email@mydomain.com",  // replace with valid value
                        Password = "0000"  // replace with valid value
                    };
                    smtp.Credentials = credential;
                    smtp.Host = "relay-hosting.secureserver.net";
                    smtp.Port = 25;
                    smtp.Timeout = 1000;
                    smtp.DeliveryMethod = SmtpDeliveryMethod.Network;
                    smtp.UseDefaultCredentials = false;
                    smtp.SendCompleted += (s, e) =>
                    {
                        //delete attached files
                        foreach (var path in paths)
                            System.IO.File.Delete(path);
                    };
                    await smtp.SendMailAsync(message);
                    ViewBag.Message = "Your message has been sent!";

                    ModelState.Clear();
                    return View("Index");
                }
            }
            else
            {
                TempData["recaptcha"] = "Please verify that you are not a robot!";
            }

        } return View(model);

        }

        catch (Exception ex)
        {
            return View("Error");
        }

    }
Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Kendall H.
  • 461
  • 3
  • 9
  • 21
  • What is the return type of - `smtp.SendMailAsync`, program is hanging due to deadlock as you are not handling the Synchronization context correctly. What are you doing with `Task` and also check for a blocking point inside SendMail function – Mrinal Kamboj Jul 15 '16 at 07:18
  • In case you are using `Task.WaitAll` for the return of the `public async Task Index` , replace it by `Task.WhenAll` or `await`, to avoid blocking synchronization context. preferably `await`, but that would need whole call chain to be `async` – Mrinal Kamboj Jul 15 '16 at 07:22
  • First try to reproduce this problem without attachments. – Jeroen Heier Jul 15 '16 at 07:34
  • @MrinalKamboj I'm using `await` to return `smtp.SendMailAsync` Sir. I'm a bit confuse cause when I run it in localhost before the mail is sending properly, but now it's not returning. I can't quite understand. Please be patient with me Sir. Thank you for helping me. – Kendall H. Jul 15 '16 at 07:35
  • @JeroenHeier **EDIT** when I tried to comment out the attachment part, and send it. ViewBag appears, it just take couple of mins to be receive tho. But it's there. Why is that Sir? Please be patient with me, I'm just a newbie. Thank you for you help. – Kendall H. Jul 15 '16 at 08:33
  • 1
    Try to test your code without the SendCompleted implementation. I think the delete operation cannot be completed because of file locking. Dispose your Attachment objects first. – Jeroen Heier Jul 15 '16 at 08:49
  • 1
    @Kendall H I am not referring to that await, the `public async Task Index` method is also `Async`, how is this called, that would need await. Also can you run a test without `Async`, completely Synchronous version will that work. Need better understanding of the issue, this is deadlock due to blocking of Synchronization Context – Mrinal Kamboj Jul 15 '16 at 08:53
  • @JeroenHeier Seems like that's the problem Sir! The permission to delete from that folder in the File Manager in my Hosting Account is not allowed. Thank you so much for helping me out. :) The ViewBag appears. Just have to wait for the mail to be received. I'll update you. Again thank you. :) – Kendall H. Jul 15 '16 at 09:05
  • @JeroenHeier It really did work. Thank you so much Sir. :) – Kendall H. Jul 15 '16 at 09:40

1 Answers1

2

I have ran into this same issue before. The Attachments are being held onto and need to be disposed of before you can delete them. Without the call to .Dispose the files are locked down. Try this instead:

[
    HttpPost,
    ValidateAntiForgeryToken
]
public async Task<ActionResult> Index(EmailFormModel model, 
                                      IEnumerable<HttpPostedFileBase> files)   
{
    try
    {
        if (ModelState.IsValid)
        {
            string EncodedResponse = Request.Form["g-Recaptcha-Response"];
            bool IsCaptchaValid = ReCaptcha.Validate(EncodedResponse) == "True";
            if (IsCaptchaValid)
            {
                var paths = GetUploadPaths(files);    
                using (var message = ConstructMailMessage(model))
                {
                    AppendAttachments(paths, message.Attachments);

                    using (var smtp = new SmtpClient())
                    {
                        var credential = new NetworkCredential
                        {
                            UserName = "...", // replace with valid value
                            Password = "..."  // replace with valid value
                        };
                        smtp.Credentials = credential;
                        smtp.Host = "relay-hosting.secureserver.net";
                        smtp.Port = 25;
                        smtp.Timeout = 1000;
                        smtp.DeliveryMethod = SmtpDeliveryMethod.Network;
                        smtp.UseDefaultCredentials = false;
                        smtp.SendCompleted += (s, e) =>
                        {
                            // Ensure disposed first.
                            foreach (var a in message.Attachments) a.Dispose();

                            foreach (var path in paths) File.Delete(path);
                        };

                        await smtp.SendMailAsync(message);

                        ViewBag.Message = "Your message has been sent!";

                        ModelState.Clear();
                        return View("Index");
                    }
                }
            }
            else
            {
                TempData["recaptcha"] = "Please verify that you are not a robot!";
            }

        }
        return View(model);
    }
    catch (Exception ex)
    {
        return View("Error");
    }
}

I tried a slightly different approach with regard to separating some of the core logic. For example, there is now a helper method for getting the upload file paths GetUploadPaths and one for appending attachments the the .Attachments instance via the AppendAttachments. Additionally, there is now a ConstructMailMessage function that does that work as well.

public List<string> GetUploadPaths(IEnumerable<HttpPostedFileBase> files)
{
    var paths = new List<string>();
    foreach (var file in files)
    {
        if (file.ContentLength > 0)
        {
            var fileName = Path.GetFileName(file.FileName);
            var path = Path.Combine(HttpContext.Current.Server.MapPath("~/App_Data/uploads"), fileName);
            file.SaveAs(path);
            paths.Add(path);
        }
    }

    return paths;
}

public MailMessage ConstructMailMessage(EmailFormModel model)
{
     var message = new MailMessage();
     var body = "<p><b>Email From:</b> {0} ({1})</p><p><b>Subject:</b> {2} </p><p><b>Software Description:</b></p><p>{4}</p><p><b>Message:</b></p><p>{3}</p>";
     message.To.Add(new MailAddress("email@mydomain.com"));  // replace with valid value 
     message.From = new MailAddress("email@randomdomain.com");  // replace with valid value
     message.Subject = "(Inquire for SELLING)";
     message.Body = string.Format(body, model.FromName, model.FromEmail, model.FromSubject, model.Message, model.Desc);
     message.IsBodyHtml = true;

     return message;
}

public void AppendAttachments(List<string> paths, AttachmentCollection attachments)
{
    foreach (var path in paths)
    {
        var fileInfo = new FileInfo(path);
        var memoryStream = new MemoryStream();
        using (var stream = fileInfo.OpenRead())
        {
            stream.CopyTo(memoryStream);
        }
        memoryStream.Position = 0;
        string fileName = fileInfo.Name;
        attachments.Add(new Attachment(memoryStream, fileName));
    }
}
David Pine
  • 23,787
  • 10
  • 79
  • 107