Отправка асинхронного электронного письма без ожидания от основного веб-сервиса.Net
У меня есть веб-сервис.Net core2, в котором есть определенные методы отправки электронной почты. У меня это работает нормально, используя smtpclient.sendemailasync
,
public async Task<bool> SendEmailAsync(MailMessage email)
{
try
{
if (string.IsNullOrWhiteSpace(emailFrom)) email.From = new MailAddress(emailFrom);
using (SmtpClient client = getSMTPClientInstance())
{
await client.SendMailAsync(email);
}
return true;
}
catch (Exception ex)
{
Log.Error(ex, "Error sending email in EmailService.SendEmailAsync");
return false;
}
}
Единственная проблема заключается в том, что некоторые SMTP-серверы реагируют слишком долго. Я хочу настроить электронную почту, поставить ее в очередь и вернуться, не дожидаясь результата.
Простое использование незапланированной асинхронности не доступно по двум причинам;
Не надежно продолжать метод вне контекста запроса в asp
Мне нужен доступ к контексту базы данных моей структуры сущностей, чтобы написать журнал
Я должен разрешить внешний или внутренний SMTP (мой клиент указывает), поэтому папка коллекции не возможна - по крайней мере, без службы, которая управляет ею.
Как я мог этого добиться? Мне нужно написать сервис, который управляет этим? Если да, то как мне это сделать в моем.Net Core App, имея в виду, что службе также необходим доступ к контексту EF для записи журнала
ОБНОВИТЬ
Специально для этого в.NetCore DI есть сантехника. Обратитесь к моему дополнительному ответу ниже. Используйте IServiceScopeFactory
2 ответа
Вы можете позвонить RegisterAsyncTask
метод на объекте Page. Это будет сигнализировать время выполнения ASP.NET, что вы хотите убедиться, что они завершены, прежде чем завершать контекст запроса:
Пример:
public void Page_Load(object sender, EventArgs e)
{
RegisterAsyncTask(new PageAsyncTask(LoadSomeData));
}
public async Task LoadSomeData()
{
var clientcontacts = Client.DownloadStringTaskAsync("api/contacts");
var clienttemperature = Client.DownloadStringTaskAsync("api/temperature");
var clientlocation = Client.DownloadStringTaskAsync("api/location");
await Task.WhenAll(clientcontacts, clienttemperature, clientlocation);
var contacts = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Contact>>(await clientcontacts);
var location = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(await clientlocation);
var temperature = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(await clienttemperature);
listcontacts.DataSource = contacts;
listcontacts.DataBind();
Temparature.Text = temperature;
Location.Text = location;
}
Итак, хотя я отметил ответ, есть пара вариантов, которые являются лучшими решениями для моего конкретного примера. Во-первых, это возможность использовать библиотеку типа hangfire для планирования задач, хотя технически это не является ответом на вопрос.
Лучшее решение в ядре.net - использовать IServiceScopeFactory
С IServiceScopeFactory вы можете изменить объем задачи, чтобы она не выходила за рамки, когда запрос завершен. Я сделал следующее непосредственно в контроллере (позже я перешел к использованию подхода hangfire, но это работает). Как вы можете видеть, асинхронная задача запускается в новом нежелательном потоке, пока код контроллера продолжается.
var task = Task.Run(async () =>
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var service = scope.ServiceProvider.GetRequiredService<ApprovalService>();
await service.sendResponseEmailAsync(approvalInfo.ApprovalId, userID, approvalInfo.emailTo, approvalInfo.ccTo);
}
});