Подтвердить что ты не робот

.SendMailAsync() используется в MVC

Я пытаюсь отправить электронное письмо из своего приложения MVC, оно отлично передает, когда я использую метод .Send(), но требуется некоторое время, чтобы вернуться, поэтому я хотел использовать функцию .SendMailAsync(), но я получаю после ошибки во время выполнения.

Асинхронная операция не может быть запущена в это время. Асинхронные операции могут запускаться только в асинхронном обработчике или модуле или во время определенных событий в жизненном цикле страницы. Если это исключение произошло во время выполнения страницы, убедитесь, что страница отмечена <% @Page Async = "true" % >

Это мой пример кода. Как я могу настроить это для отправки с помощью .SendMailAsync()

Класс Wrapper Email:

using System.Net.Mail;

namespace Helpers
{
    public class Email
    {
        // constants
        private const string HtmlEmailHeader = "<html><head><title></title></head><body style='font-family:arial; font-size:14px;'>";
        private const string HtmlEmailFooter = "</body></html>";

        // properties
        public List<string> To { get; set; }
        public List<string> CC { get; set; }
        public List<string> BCC { get; set; }
        public string From { get; set; }
        public string Subject { get; set; }
        public string Body { get; set; }  

        // constructor
        public Email()
        {
            To = new List<string>();
            CC = new List<string>();
            BCC = new List<string>();
        }

        // send
        public void Send()
        {
            MailMessage message = new MailMessage();

            foreach (var x in To)
            {
                message.To.Add(x);
            }
            foreach (var x in CC)
            {
                message.CC.Add(x);
            }
            foreach (var x in BCC)
            {
                message.Bcc.Add(x);
            }

            message.Subject = Subject;
            message.Body = string.Concat(HtmlEmailHeader, Body, HtmlEmailFooter);
            message.BodyEncoding = System.Text.Encoding.UTF8;
            message.From = new MailAddress(From);
            message.SubjectEncoding = System.Text.Encoding.UTF8;
            message.IsBodyHtml = true;

            SmtpClient client = new SmtpClient("relay.mail.server");

            client.SendMailAsync(message);            
        }
    }
}

Контроллер:

public ActionResult Index()
    {

        Email email = new Email();
        email.To.Add("[email protected]");
        email.From = "[email protected]";
        email.Subject = "Subject";
        email.Body = "<p><strong>Hello</strong></p><p>This is my first Email Message</p>";
        email.Send();
    }

ИЗМЕНИТЬ

В дополнение к актуальному вопросу, основная проблема заключалась в задержке, созданной при отправке писем. Я смотрел дальше в актуальную проблему и с помощью этого сообщения:

ASP.Net MVC фоновые потоки для создания и отправки электронной почты

изменил класс Email Wrapper, чтобы создать новый поток для обработки электронной почты:

using System.Net.Mail;

namespace Helpers
{
    public class Email
    {
        // constants
        private const string HtmlEmailHeader = "<html><head><title></title></head><body style='font-family:arial; font-size:14px;'>";
        private const string HtmlEmailFooter = "</body></html>";

        // properties
        public List<string> To { get; set; }
        public List<string> CC { get; set; }
        public List<string> BCC { get; set; }
        public string From { get; set; }
        public string Subject { get; set; }
        public string Body { get; set; }  

        // constructor
        public Email()
        {
            To = new List<string>();
            CC = new List<string>();
            BCC = new List<string>();
        }

        // send
        public void Send()
        {
            MailMessage message = new MailMessage();

            foreach (var x in To)
            {
                message.To.Add(x);
            }
            foreach (var x in CC)
            {
                message.CC.Add(x);
            }
            foreach (var x in BCC)
            {
                message.Bcc.Add(x);
            }

            message.Subject = Subject;
            message.Body = string.Concat(HtmlEmailHeader, Body, HtmlEmailFooter);
            message.BodyEncoding = System.Text.Encoding.UTF8;
            message.From = new MailAddress(From);
            message.SubjectEncoding = System.Text.Encoding.UTF8;
            message.IsBodyHtml = true;

            SmtpClient client = new SmtpClient("relay.mail.server");

            new Thread(() => { client.Send(message); }).Start();        
        }
    }
}
4b9b3361

Ответ 1

По общему признанию, ошибка немного туповата, но все, что она действительно говорит вам, заключается в том, что вы вызываете асинхронный метод из синхронного метода, что недопустимо. Если вы собираетесь использовать async, вы должны использовать асинхронный путь до цепочки.

Итак, сначала вам нужно изменить определение метода Send, чтобы вернуть Task:

public async Task Send()

И настройте вызов метода асинхронного вызова:

await client.SendMailAsync(message);

Затем сделайте то же самое для своего действия:

public async Task<ActionResult> Index()

и

await email.Send();

UPDATE

Async не делает то, что я думаю, вы думаете, что это так. Когда ваше действие вызывается запросом, оно не будет возвращать ответ, пока весь код внутри действия не будет полностью выполнен. Async не волшебная палочка, которая заставляет действие быстрее реагировать. Ваша задача (в этом случае отправка электронной почты) занимает столько времени, сколько потребуется, и асинхронно или нет, действие не вернет ответ до завершения задачи.

Итак, зачем использовать async? Потому что то, что делает async, это отпустить поток из пула серверов. Скажем, что IIS работает в довольно стандартной конфигурации, вы, вероятно, будете иметь около 1000 потоков. Это часто называют "максимальными запросами", потому что обычно 1 запрос == 1 поток. Таким образом, если сервер находится под большой нагрузкой, и вы отправляете больше, чем "максимальные запросы", каждый последующий запрос ставится в очередь до тех пор, пока поток из пула снова не станет доступен. Если все потоки привязаны, ожидая чего-то, чтобы завершить, ваш сервер по существу блокирует. Но, когда вы используете async, вы, по сути, говорите IIS: "Я чего-то жду. Здесь мой поток назад, поэтому вы можете использовать его для ввода другого запроса. Я дам вам знать, когда мне это понадобится". Это позволяет выполнять запросы в очереди.

Длинные и короткие, всегда используйте async, когда вы делаете что-то, что связано с ожиданием, потому что оно позволяет использовать ресурсы сервера более эффективно, но помните, что это не ускоряет работу.

РЕДАКТИРОВАТЬ 12/11/14. Обновлена ​​терминология, чтобы пояснить, что асинхронный вызов полезен только тогда, когда поток ожидает, а не просто участвует в какой-то долговременной задаче. Например, выполнение сложных финансовых вычислений может занять некоторое время, но не подходит для async, потому что вся работа связана с ЦП. Задача может быть длительной, но если поток не находится в состоянии ожидания, он не может использоваться для других задач, и ваш метод async будет по существу просто запускаться как синхронизация, но с дополнительными служебными данными.

Ответ 2

Это может помочь вам.

    public void Send(MailAddress toAddress, string subject, string body, bool priority)
    {
        Task.Factory.StartNew(() => SendEmail(toAddress, subject, body, priority), TaskCreationOptions.LongRunning);
    }

    private void SendEmail(MailAddress toAddress, string subject, string body, bool priority)
    {
        MailAddress fromAddress = new MailAddress(WebConfigurationManager.AppSettings["SmtpFromAddress"]);
        string serverName = WebConfigurationManager.AppSettings["SmtpServerName"];
        int port = Convert.ToInt32(WebConfigurationManager.AppSettings["SmtpPort"]);
        string userName = WebConfigurationManager.AppSettings["SmtpUserName"];
        string password = WebConfigurationManager.AppSettings["SmtpPassword"];

        var message = new MailMessage(fromAddress, toAddress);

        message.Subject = subject;
        message.Body = body;
        message.IsBodyHtml = true;
        message.HeadersEncoding = Encoding.UTF8;
        message.SubjectEncoding = Encoding.UTF8;
        message.BodyEncoding = Encoding.UTF8;
        if (priority) message.Priority = MailPriority.High;

        Thread.Sleep(1000);

        SmtpClient client = new SmtpClient(serverName, port);
            client.DeliveryMethod = SmtpDeliveryMethod.Network;
            client.EnableSsl = Convert.ToBoolean(WebConfigurationManager.AppSettings["SmtpSsl"]);
            client.UseDefaultCredentials = false;

            NetworkCredential smtpUserInfo = new NetworkCredential(userName, password);
            client.Credentials = smtpUserInfo;

            client.Send(message);

            client.Dispose();
            message.Dispose();
    }

The Thread.Sleep существует, потому что это будет отправлять почту так быстро, что многие SMTP-серверы будут сообщать слишком много сообщений электронной почты из одного и того же сообщения об ошибке IP. Хотя ASP.NET обрабатывает асинхронную отправку почты, он не будет отправлять более одного сообщения за раз. Он ожидает, пока обратный вызов не появится перед отправкой другого сообщения. Этот подход будет отправлять сообщения параллельно так же быстро, как код может вызвать Send().

Ответ 3

Я думаю, что нижеследующее делает то, что вы пытаетесь выполнить:

Измените свой контроллер следующим образом:

public async Task<ActionResult> Index()
{
    Email email = new Email();
    email.SendAsync();
}

И в вашем классе электронной почты добавьте метод SendAsync, как показано ниже

public async Task SendAsync()
{
    await Task.Run(() => this.send());
}

Действие вернется до отправки сообщения электронной почты, и запрос не будет заблокирован.

Попробуйте увидеть поведение с шаблоном mvc по умолчанию с помощью этого кода:

[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
    LongRunningTaskAsync();
    return View();
}

public static async Task LongRunningTaskAsync()
{
    await Task.Run(() => LongRunningTask());
}

public static void LongRunningTask()
{
    Debug.WriteLine("LongRunningTask started");
    Thread.Sleep(10000);
    Debug.WriteLine("LongRunningTask completed");
}

Страница входа в систему будет отображаться мгновенно. Но в окне вывода появится "LongRunningTask completed" через 10 секунд.

Общее время доставки может быть более длинным с помощью этого метода, как указано в комментариях, но я думаю, что OP пытался немедленно вернуть действие и отправить электронное письмо в фоновом режиме.