【问题标题】:How to make a IHostingService to send emails on order confirm?如何让 IHostingService 在订单确认时发送电子邮件?
【发布时间】:2021-01-20 02:04:15
【问题描述】:

我有一个 ac#WebApi 项目,用户可以在网站下订单,支付完成后的每个订单都会执行一个名为ConfirmOrder的函数,它将订单状态从STB更新为COMPLETED

在如下函数中,如下所示:

public static void ConfirmOrder(string piva, string orderID, double importo = 0, string transazione = "", string paymentID = "", string tipo = "MENU")
{
    string connectionString = getConnectionString(piva);
    using var connection = new MySqlConnection(connectionString);

    string query_menu = "QUERY";
    string query_pagamenti = "QUERY";
    using var cmd = new MySqlCommand(query_pagamenti, connection);
    connection.Open();
    cmd.Parameters.AddWithValue("@tipo", tipo.ToUpper());
    cmd.Parameters.AddWithValue("@importo", importo);
    cmd.Parameters.AddWithValue("@transazione", transazione);
    cmd.Parameters.AddWithValue("@dataOra", DateTime.Now);
    cmd.Parameters.AddWithValue("@orderID", orderID);
    cmd.Parameters.AddWithValue("@paymentID", paymentID);
    cmd.Prepare();
    cmd.ExecuteNonQuery();

    cmd.CommandText = query_menu;
    cmd.ExecuteNonQuery();

    if (!tipo.Equals("MENU"))
    {
        EmailHelper.SendRiepilogo(piva, int.Parse(orderID)); // SENDING SUMMARY MAIL
    }
    
}

我正在调用另一个函数SendRiepilogo,它向用户和商店发送摘要,但在这种情况下,我不能等待该函数响应,但它必须自己执行而不卡住ConfirmOrder回调..所以我等不及要执行SendRiepilogo,此时我已经阅读了IHostingService,但我不知道如何将我的SendRiepilogo迁移到IHostingService并从ConfirmOrder运行它...

我的SendRiepilogo 看起来像这样:

public static async void SendRiepilogo(string piva, int idOrdine)
{
    var order = GetOrdine(piva, idOrdine);
    if (order == null)
    {
        return;
    }
    try
    {
        var negozio = getNegozio(order.idNegozio);
        var from = new MailAddress("ordini@visualorder.it", "VisualOrder");
        var to = new MailAddress(order.cliente.FirstOrDefault().email);

        using MemoryStream ms = new MemoryStream();
        QRCodeGenerator qrGenerator = new QRCodeGenerator();
        QRCodeData qrCodeData = qrGenerator.CreateQrCode("vo/" + idOrdine, QRCodeGenerator.ECCLevel.Q);
        Base64QRCode qrCode = new Base64QRCode(qrCodeData);
        byte[] byteQr = Convert.FromBase64String(qrCode.GetGraphic(20));
        MemoryStream streamQr = new MemoryStream(byteQr);
        var qrImage = new LinkedResource(streamQr, MediaTypeNames.Image.Jpeg)
        {
            ContentId = "qrImage"
        };
        string nome = order.cliente.FirstOrDefault().nome;
        var orderEmail = new { idOrdine, order, nome, negozio };

        byte[] byteLogo = Convert.FromBase64String(System.Text.Encoding.UTF8.GetString(negozio.logo));
        MemoryStream streamLogo = new MemoryStream(byteLogo);
        var logoImage = new LinkedResource(streamLogo, MediaTypeNames.Image.Jpeg)
        {
            ContentId = "logoImage"
        };



        string template = File.ReadAllText("Views/Emails/EmailRiepilogo.cshtml");
        var htmlBody = Engine.Razor.RunCompile(template, "riepilogo", null, orderEmail);

        AlternateView alternateView = AlternateView.CreateAlternateViewFromString(htmlBody, null, MediaTypeNames.Text.Html);
        alternateView.LinkedResources.Add(qrImage);
        alternateView.LinkedResources.Add(logoImage);


        var message = new MailMessage(from, to)
        {
            Subject = "Riepilogo ordine",
            Body = htmlBody
        };
        message.IsBodyHtml = true;
        message.AlternateViews.Add(alternateView);

        using var smtp = new SmtpClient("smtps.aruba.it", 587)
        {
            EnableSsl = true,
            Credentials = new NetworkCredential("XXX", "XXX")
        };
        await smtp.SendMailAsync(message); // sending email to user
        await smtp.SendMailAsync(MessageNegozio(order, idOrdine, negozio)); // sending email to shop
    }
    catch (Exception e)
    {
        return;
    }

    ConfirmEmail(piva, idOrdine); // setting "EMAIL SENT" flag in DB to true
    return;
}

【问题讨论】:

  • 你试过了吗Task.Run(() => SendRiepilogo())stackoverflow.com/a/17805992/575468
  • @dreijntjens 我收到了An asynchronous call is already in progress. It must be completed or canceled before you can call this method.,我已经从 smtp.SendMailAsync 中删除了 await 并让函数不异步执行 Task.Run
  • @dreijntjens 在执行 Task.Run 时我应该保留 awaitasync 吗?
  • 不要混合使用异步和同步,如果不是 EventHandler,不要使用async void(极少数例外)。
  • 您无法使用 Task.Run 解决此问题。后台服务是完全不同的服务,使用自己的线程。你必须告诉它该做什么并让它完成工作。顺便说一句,这就是文档显示的内容

标签: c#


【解决方案1】:

后台(托管)服务是一个完全不同的服务,使用它自己的线程来完成它的工作。你不能让你的控制器在那个服务上“运行”一些东西,你必须告诉它要做什么,然后让它去做。

文档中的Background tasks with hosted services 部分显示了长时间运行的后台服务可以工作的两种不同方式:

  • 只要应用程序正在运行,定时服务就可以在每次触发计时器时运行并执行定期作业
  • queued service 等待队列中的消息并在消息到达时执行作业

发送电子邮件适合第二种情况。您几乎可以按原样使用文档示例。您可以创建一个IBackgroundTaskQueue 接口,像您的控制器这样的客户端可以使用该接口来提交作业以在后台运行:

public interface IBackgroundTaskQueue
{
    void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);

    Task<Func<CancellationToken, Task>> DequeueAsync(
        CancellationToken cancellationToken);
}

此接口可以作为依赖项添加到容器的构造函数中。 假设注入的服务名为myJobQueue,控制器可以将作业排入队列以在后台运行:


IBackgroundTaskQueue _myJobQueue

public MyController(IBackgroundTaskQueue myJobQueue)
{
    _myJobQueue=myJobQueue;
}


public void ConfirmOrder(...)
{
    ...
    if (!tipo.Equals("MENU"))
    {
        var ordId=int.Parse(orderID);
    
  
  _myJobQueue.QueueBackgroundWorkItem(ct=>EmailHelper.SendRiepilogoAsync(piva,ordId )); 
}

async void 只能用于异步事件处理程序。 SendRiepilogo 不是这样。 async void 方法不能被等待,它们本质上是可能永远不会运行的即发即弃的方法,因为应用程序不知道它必须等待它们。 正确的语法应该是:

public static async Task SendRiepilogoAsync(string piva, int idOrdine)
{
...
}

文档示例的其余部分可以按原样使用。

简化服务

您可以创建一个仅接受特定消息类、仅接受地址和订单 ID 的队列,而不是运行任何可用作业的通用排队服务,并让 服务 完成检索工作任何数据并发送电子邮件。本质上,SendRiepilogoAsync 成为后台服务的一部分。这允许创建可以批量发送电子邮件、同时发送多封电子邮件、应用限制等的服务。

这将允许重用昂贵的资源或只执行一次昂贵的操作,例如创建 SmptClient 并在开始处理队列消息之前进行身份验证

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-09-25
    • 1970-01-01
    • 1970-01-01
    • 2016-01-23
    • 1970-01-01
    • 2021-06-16
    • 2017-12-27
    • 2015-02-11
    相关资源
    最近更新 更多