如何解决在ASP.NET MVC中发送多种电子邮件类型的最佳方法 不同的行业标准方法改善自己的能力更多改进
嗨,SO的好朋友!
这更多是设计问题,因此我将详细介绍示例。
让我解释一下我们发送电子邮件的方式。
在应用程序的各个部分,我们在Notification
表中为可能必须发送的各种电子邮件创建条目。
例如: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; }
不同类型的电子邮件以enum
为模型:
public enum TypeOfEmail
{
OrderCancellation,OrderUpdate
}
我们有一个EmailModel
类,该类具有一个TicketsInNotificationQueue
属性,该属性列出了我们拥有的任何电子邮件类型。例如:在任何给定时间,它可以具有UpdatedTickets
或CancelledTickets
的列表。电子邮件类型说明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,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
的部分(以获取用于notificationIds
更新的相应SentDateTime
)。
因此,还有其他方法可以设计此工作流程(我什至可以使用反射等方法)以使其更易于管理和简洁?
任何帮助将不胜感激!
解决方法
您可以对现有系统和现有代码进行重大改进。为了获得更完整的答案,我将建议对系统进行一次不太昂贵的大修,然后继续进行您的确切回答。
不同的行业标准方法
您已经具有正确的数据结构,这对于分布式持久队列来说是一项完美的工作,您无需担心查询数据库那么多;取而代之的是,您只排队消息,并拥有处理它们的处理器。由于您使用的是C#和.net,因此强烈建议您查看Azure Service Bus。实际上,这是一个很大的队列,您可以在其中发送消息(在这种情况下,发送电子邮件请求),并且可以根据消息的类型将消息排队到服务总线中的不同通道。
您还可以考虑创建一个开箱即用的Azure Functions have a trigger队列处理器。发送电子邮件后,您就可以写您的数据库了,我们已经发送了此电子邮件。
所以,好的设计看起来像
- 具有分布式持久队列,渠道/将电子邮件请求直接排入队列。
- 如果要以节奏进行处理,请使用大多数行业解决方案支持的cron运行处理器。
- 如果要在队列中处理它们时进行处理,请使用触发器。
您可以根据自己的情况来丰富处理器,这似乎与订单有关,因此您可能需要处理一些情况,例如在取消订单后不发送已经排队的电子邮件等。
改善自己的能力
由于某些情况,您可能无法使用上述解决方案-因此,让我们开始吧。
查看如何重构switch语句(因为您有if
/ else if
s个语句)
您可以通过多态性来做到这一点,只需创建基本邮件类型并覆盖子类中的行为即可。这样,您可以将正确的队列与正确的电子邮件类型相关联。
示例:
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())
在此循环中,您将不必要地一遍又一遍地检查电子邮件类型,因为您已经知道要查询的类型,因此您应考虑将这些语句移到for上方并检查传递的参数。 / p>
,谢谢您的回答@Mavi Domates。
但这是我最终要做的:
我修改了EmailModel
的{{1}}属性,以便为不同类型的电子邮件提供不同类型的类,而不是只有一种普通类。这样可以避免让我们进行检查,以检查在提取逻辑中请求了哪种电子邮件,并避免在行中检索TicketsInNotificationQueue
(在发送电子邮件后更新notification Ids
),如原始问题。
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
,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);
参数传递给EmailModel
构造函数。无需做所有额外的检查来确定是否请求了某些ticketsInNotificationQueue
的记录。 emailType
和OrderCancellation
的视图将使用OrderUpdate
类中存在的公共属性及其各自的相关属性。
CommonEmailModel
现在我要做的就是将if (emailRecords.Any())
{
var emailModel = new EmailModel(emailType,emailRecords);
}
传递给一个方法,该方法只需调用以下命令即可将当前时间戳记为notification Ids
列:
SentDateTime
将来,如果我们继续添加新的if (emailWasSent)
{
await UpdateNotificationSentTimeAsync(emailRecords.Select(t => t.NotificationQueueId));
}
(很可能它们将在emailType
的前四个公共属性中携带信息),我们可以简单地向{{ 1}}来容纳它,然后创建一个新视图。这样,在更新CommonEmailModel
时,我就可以避免代码的重复和复杂性,也可以避免最后的代码。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。