使用带有此处描述的命令模式和此处描述的查询模式的简单注入器。对于其中一个命令,我有 2 个处理程序实现。第一个是同步执行的“正常”实现:
public class SendEmailMessageHandler
: IHandleCommands<SendEmailMessageCommand>
{
public SendEmailMessageHandler(IProcessQueries queryProcessor
, ISendMail mailSender
, ICommandEntities entities
, IUnitOfWork unitOfWork
, ILogExceptions exceptionLogger)
{
// save constructor args to private readonly fields
}
public void Handle(SendEmailMessageCommand command)
{
var emailMessageEntity = GetThisFromQueryProcessor(command);
var mailMessage = ConvertEntityToMailMessage(emailMessageEntity);
_mailSender.Send(mailMessage);
emailMessageEntity.SentOnUtc = DateTime.UtcNow;
_entities.Update(emailMessageEntity);
_unitOfWork.SaveChanges();
}
}
另一个类似于命令装饰器,但显式包装了前一个类以在单独的线程中执行命令:
public class SendAsyncEmailMessageHandler
: IHandleCommands<SendEmailMessageCommand>
{
public SendAsyncEmailMessageHandler(ISendMail mailSender,
ILogExceptions exceptionLogger)
{
// save constructor args to private readonly fields
}
public void Handle(SendEmailMessageCommand command)
{
var program = new SendAsyncEmailMessageProgram
(command, _mailSender, _exceptionLogger);
var thread = new Thread(program.Launch);
thread.Start();
}
private class SendAsyncEmailMessageProgram
{
internal SendAsyncEmailMessageProgram(
SendEmailMessageCommand command
, ISendMail mailSender
, ILogExceptions exceptionLogger)
{
// save constructor args to private readonly fields
}
internal void Launch()
{
// get new instances of DbContext and query processor
var uow = MyServiceLocator.Current.GetService<IUnitOfWork>();
var qp = MyServiceLocator.Current.GetService<IProcessQueries>();
var handler = new SendEmailMessageHandler(qp, _mailSender,
uow as ICommandEntities, uow, _exceptionLogger);
handler.Handle(_command);
}
}
}
有一段时间 simpleinjector 对我大喊大叫,告诉我它找到了IHandleCommands<SendEmailMessageCommand>
. 我发现以下方法有效,但不确定它是否是最佳/最佳方式。我想显式注册这个接口以使用 Async 实现:
container.RegisterManyForOpenGeneric(typeof(IHandleCommands<>),
(type, implementations) =>
{
// register the async email handler
if (type == typeof(IHandleCommands<SendEmailMessageCommand>))
container.Register(type, implementations
.Single(i => i == typeof(SendAsyncEmailMessageHandler)));
else if (implementations.Length < 1)
throw new InvalidOperationException(string.Format(
"No implementations were found for type '{0}'.",
type.Name));
else if (implementations.Length > 1)
throw new InvalidOperationException(string.Format(
"{1} implementations were found for type '{0}'.",
type.Name, implementations.Length));
// register a single implementation (default behavior)
else
container.Register(type, implementations.Single());
}, assemblies);
我的问题:这是正确的方法,还是有更好的方法?例如,我想将 Simpleinjector 抛出的现有异常重用于所有其他实现,而不必在回调中显式抛出它们。
更新对史蒂文的回答的回复
我已经更新了我的问题,使其更加明确。我以这种方式实现它的原因是因为作为操作的一部分,该命令会在成功发送后更新在 db 实体上System.Nullable<DateTime>
调用的属性。SentOnUtc
MailMessage
和都由实体框架类实现。根据 http 上下文注册,使用ICommandEntities
此处描述的方法:IUnitOfWork
DbContext
DbContext
container.RegisterPerWebRequest<MyDbContext>();
container.Register<IUnitOfWork>(container.GetInstance<MyDbContext>);
container.Register<IQueryEntities>(container.GetInstance<MyDbContext>);
container.Register<ICommandEntities>(container.GetInstance<MyDbContext>);
RegisterPerWebRequest
simpleinjector wiki 中扩展方法的默认行为是在HttpContext
为 null 时注册一个临时实例(它将在新启动的线程中)。
var context = HttpContext.Current;
if (context == null)
{
// No HttpContext: Let's create a transient object.
return _instanceCreator();
...
这就是为什么 Launch 方法使用服务定位器模式来获取 的单个实例DbContext
,然后将其直接传递给同步命令处理程序构造函数。为了使_entities.Update(emailMessageEntity)
和_unitOfWork.SaveChanges()
行工作,两者必须使用相同的 DbContext 实例。
注意:理想情况下,发送电子邮件应由单独的投票工作人员处理。这个命令基本上是一个队列清算所。数据库中的 EmailMessage 实体已经拥有发送电子邮件所需的所有信息。这个命令只是从数据库中抓取一个未发送的,发送它,然后记录动作的日期时间。这样的命令可以通过从不同的进程/应用程序轮询来执行,但我不会接受这个问题的这样的答案。现在,当某种 http 请求事件触发它时,我们需要启动这个命令。