簡體   English   中英

在 ASP.NET MVC 中發送多個 email 類型的最佳方式

[英]Best way to send multiple email types in ASP.NET MVC

SO 的好朋友們大家好!

這更像是一個設計問題,所以我將進入一個詳細的例子。

讓我解釋一下我們發送電子郵件的方式。 在應用程序的各個部分,我們在Notification表中為我們可能必須發送的不同類型的 email 創建條目。 例如: NotificationQueue表如下所示:

NotificationQueueID OrderID         EmailType           Notes       SentDatetime
1                   461196          OrderUpdate         SomeNote1   2020-09-01 14:45:13.153
2                   461194          OrderCancellation   SomeNote2   2020-09-01 14:45:13.153

使用 DbContext 中的屬性訪問它:

public DbSet<NotificationQueue> NotificationQueues { get; set; }

email 的不同類型在enum中建模:

public enum TypeOfEmail
{
    OrderCancellation,
    OrderUpdate
}

我們有一個EmailModel class,它有一個TicketsInNotificationQueue屬性,該屬性包含我們擁有的任何 email 類型的列表。 例如:在任何給定時間,它都可以有UpdatedTicketsCancelledTickets的列表。 email 類型表示TicketsInNotificationQueue屬性中的票證類型。

public class EmailModel
{
    public EmailModel(TypeOfEmail emailType, TicketsInNotificationQueue ticketsInNotificationQueue)
    {
        EmailType = emailType;
        TicketsInNotificationQueue = ticketsInNotificationQueue;
    }

    public TypeOfEmail EmailType { get; set; }
    public TicketsInNotificationQueue TicketsInNotificationQueue { get; set; }
}

public class TicketsInNotificationQueue
{
    public List<OrderCancellation> CancelledTickets { get; set; }
    public List<OrderUpdate> UpdatedTickets { get; set; }
}

public class OrderCancellation : CommonOrderInformation
{
    public string SomeOrderId { get; set; }
}

public class OrderUpdate: CommonOrderInformation
{
    public string SomeUpdateRelatedProperty { get; set; }
}

public class CommonOrderInformation
{
    public int NotificationQueueId { get; set; }
    public string ReferenceNumber { get; set; }
}

有一種方法可以從Notification表中檢索票證:

public async Task<TicketsInNotificationQueue> GetTicketsfromNotificationQueueAsync(TypeOfEmail emailType)
{
    var ticketsInNotificationQueue = new TicketsInNotificationQueue();

    using (var dbCon = GetSomeDbContext())
    {
        var notifications = dbCon.NotificationQueues.Where(x => x.EmailType == emailType.ToString()).ToList();

        foreach (var ntf in notifications)
        {
            if (ntf.EmailType == TypeOfEmail.OrderCancellation.ToString())
            {
                if (ticketsInNotificationQueue.CancelledTickets == null)
                {
                    ticketsInNotificationQueue.CancelledTickets = new List<OrderCancellation>();
                }
                
                ticketsInNotificationQueue.CancelledTickets.Add(new OrderCancellation()
                {
                    NotificationQueueId = ntf.NotificationQueueID,
                    ReferenceNumber = ntf.OrderID,
                    SomeOrderId = "Something from a table."
                });
            }
            else if (ntf.EmailType == TypeOfEmail.OrderUpdate.ToString())
            {
                if (ticketsInNotificationQueue.UpdatedTickets == null)
                {
                    ticketsInNotificationQueue.UpdatedTickets = new List<OrderUpdate>();
                }

                var notes = dbCon.NotificationQueues.FirstOrDefault(x => x.NotificationQueueID == ntf.NotificationQueueID)?.Notes;

                ticketsInNotificationQueue.UpdatedTickets.Add(new OrderUpdate()
                {
                    NotificationQueueId = ntf.NotificationQueueID,
                    ReferenceNumber = ntf.OrderID,
                    SomeUpdateRelatedProperty = "Something from a table."
                });
            }
        }
    }
    return ticketsInNotificationQueue;
}

現在我只需要這個列表,過濾掉我剛收到的票證類型的notificationIds ,然后繼續處理它們。 (我需要那些notificationIds在發送通知后設置SentDatetime )。

    var ticketsReceived = false;
    notificationIds = new List<int>();

    if (ticketsInNotificationQueue.CancelledTickets != null && ticketsInNotificationQueue.CancelledTickets.Any())
    {
        ticketsReceived = true;
        notificationIds = ticketsInNotificationQueue.CancelledTickets.Select(x => x.NotificationQueueId).ToList();
    }
    else if (ticketsInNotificationQueue.UpdatedTickets != null && ticketsInNotificationQueue.UpdatedTickets.Any())
    {
        ticketsReceived = true;
        notificationIds = ticketsInNotificationQueue.UpdatedTickets.Select(x => x.NotificationQueueId).ToList();
    }

    if (ticketsReceived)
    {
        // Proceed with the process of sending the email, and setting the `SentDateTime`
    }

我在這里看到的問題是,隨着emails類型的增加,比方說10-20 ,檢索票證並在以后過濾掉它們的方法需要變得如此之大,以至於它在可讀性和代碼方面會失控我根本不喜歡的可管理性。 我需要檢查提取中請求的emailType以及已收到的emailType的部分(以獲取SentDateTime更新的相應notificationIds )。 那么是否有其他方法來設計此工作流(我什至願意使用反射等)以使其更易於管理和簡潔?

任何幫助將不勝感激!

您可以對現有系統和現有代碼進行重大改進。 為了獲得更完整的答案,我將推薦一個不太昂貴的系統檢修,然后繼續您的確切答案。

一種不同的行業標准方法

您已經擁有正確的數據結構,這對於分布式持久隊列來說是一項完美的工作,您無需擔心查詢數據庫; 相反,您只需將消息排入隊列並有一個處理它們的處理器。 由於您使用的是 C# 和 .net,我強烈建議您查看Azure 服務總線 這實際上是一個大隊列,您可以在其中發送消息(在您的情況下發送 email 請求),並且您可以根據消息的類型將消息排隊到服務總線中的不同通道。

您還可以考慮創建一個隊列處理器/哪個Azure 函數具有開箱即用的觸發器 發送您的 email 后,您就可以寫入您的數據庫,我們已經發送了這個 email。

所以,好的設計看起來像

  • 有分布式持久隊列,通道/直接將 email 請求排入隊列。
  • 如果您想按節奏處理它們,請使用 cron 運行您的處理器——大多數行業解決方案都支持。
  • 如果您想在它們進入隊列時處理它們,請使用觸發器。

你可以根據你的場景豐富你的處理器,看起來它與訂單有關,所以你可能需要處理在取消訂單后不發送已經排隊的email等情況。

改善你所擁有的

由於某些情況,您可能無法使用上述解決方案 - 讓我們開始吧。

查看如何重構 switch 語句(因為你有一個帶有if / else if的語句)

您可以通過多態性來實現這一點,只需創建一個基本郵件類型並覆蓋子類中的行為。 這樣您就可以將正確的隊列與正確的 email 類型相關聯。

例子:

var results = await getSomeEmails(OrderMail);
// returns a separate processor inherited from the base one, implemented in different ways.
var processor = ProcessorFactory.Create(OrderMail);
await processor.Send(results);

更多改進

 foreach (var ntf in notifications)
        {
            if (ntf.EmailType == TypeOfEmail.OrderCancellation.ToString())

您在此循環中不必要地一遍又一遍地檢查 email 類型,您應該考慮將這些語句移到 for 上方並檢查傳入的參數,因為您已經知道要查詢的類型。

感謝@Mavi Domates 的回答。

但這就是我最終做的:我修改了EmailModelTicketsInNotificationQueue屬性,這樣我們就沒有為不同類型的 email 使用不同類型的類,我們只有一種常見的 class。這將避免讓我們進行這些檢查用於檢查在獲取邏輯中請求了哪種類型的 email 以及檢索notification Ids (在發送SentDateTime后更新 SentDateTime),如原始問題中所示。

public class EmailModel
{
    public EmailModel(TypeOfEmail emailType, IEnumerable<CommonEmailModel> ticketsInNotificationQueue)
    {
        EmailType = emailType;
        TicketsInNotificationQueue = ticketsInNotificationQueue;
    }

    public TypeOfEmail EmailType { get; set; }
    public IEnumerable<CommonEmailModel> TicketsInNotificationQueue { get; set; }
}

public enum TypeOfEmail
{
    OrderCancellation,
    OrderUpdate
}

我添加了一個名為: CommonEmailModel的新 class 並刪除了所有那些不同的 email 類型類( OrderCancellationOrderUpdate等類)。

public class CommonEmailModel
{
    // Common to all email types. A lot of email types only need these first 4 properties
    public string EmailType { get; set; }
    public int NotificationQueueId { get; set; }
    public string OrderId { get; set; }
    public string Notes { get; set; }

    // Cancellation related
    public string SomeOrderId { get; set; }

    // Update related
    public string SomeUpdateRelatedProperty { get; set; }

    public static async Task<IEnumerable<CommonEmailModel>> GetEmailBodyRecordsAsync(TypeOfEmail emailType)
    {
        var emailModels = new List<CommonEmailModel>();
        var emailEntries = await EmailNotificationQueue.GetEmailEntriesAsync(emailType);
        var relevantOrdIds = emailEntries.Select(x => x.OrderID).Distinct().ToList();

        using (var dbCon = GetSomeDbContext())
        {
            orders = dbCon.Orders.Where(x => relevantOrdIds.Contains(x.OrdNumber)).ToList();
        }

        foreach (var record in emailEntries)
        {
            var emailModel = new CommonEmailModel
            {
                EmailType = emailType,
                NotificationQueueId = record.NotificationQueueID,
                OrderId = record.OrderID,
                Notes = record.Notes,
                SomeOrderId = orders?.FirstOrDefault(o => o.OrdNumber == record.OrderID)?.SomeOrderIdINeed,
                SomeUpdateRelatedProperty = orders?.FirstOrDefault(o => o.OrdNumber == record.OrderID)?.UpdateRelatedPropertyINeed
            };

            emailModels.Add(emailModel);
        }

        return emailModels;
    }
}

我只是通過以下方式獲取記錄:

var emailRecords = await CommonEmailModel.GetEmailBodyRecordsAsync(emailType);

只需將其作為ticketsInNotificationQueue參數傳遞給EmailModel構造函數。 無需執行所有額外檢查以確定是否請求了特定emailType的記錄。 OrderCancellationOrderUpdate的視圖將使用CommonEmailModel class 中存在的公共屬性及其各自的相關屬性。

if (emailRecords.Any())
{
    var emailModel = new EmailModel(emailType, emailRecords);
}

現在我所要做的就是將notification Ids傳遞給一個方法,該方法通過簡單地調用以下方法將SentDateTime列標記為當前時間戳:

if (emailWasSent)
{
    await UpdateNotificationSentTimeAsync(emailRecords.Select(t => t.NotificationQueueId));
}

將來,如果我們繼續添加新的emailType (它們很可能會在CommonEmailModel的前 4 個通用屬性中攜帶信息),我們可以簡單地向CommonEmailModel添加新屬性以適應它並創建一個新視圖。 通過這種方式,我可以在更新SentDateTime時避免獲取代碼的重復和復雜性,也可以在最后避免代碼重復和復雜性。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM