[英]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 類型的列表。 例如:在任何給定時間,它都可以有UpdatedTickets
或CancelledTickets
的列表。 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等情況。
由於某些情況,您可能無法使用上述解決方案 - 讓我們開始吧。
查看如何重構 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 的回答。
但這就是我最終做的:我修改了EmailModel
的TicketsInNotificationQueue
屬性,這樣我們就沒有為不同類型的 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 類型類( OrderCancellation
、 OrderUpdate
等類)。
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
的記錄。 OrderCancellation
和OrderUpdate
的視圖將使用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.