Использование простого инжектора с шаблоном команды, описанным здесьи шаблоном запроса, описанным здесь. Для одной из команд у меня есть 2 реализации обработчика. Первая — это «нормальная» реализация, которая выполняется синхронно:
public class SendEmailMessageHandler
: IHandleCommands
{
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
{
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();
var qp = MyServiceLocator.Current.GetService();
var handler = new SendEmailMessageHandler(qp, _mailSender,
uow as ICommandEntities, uow, _exceptionLogger);
handler.Handle(_command);
}
}
}
Некоторое время simpleinjector кричал на меня, сообщил мне, что он нашел 2 реализации IHandleCommands
. Я обнаружил, что работает следующее, но не уверен, что это лучший/оптимальный способ.Я хочу явно зарегистрировать этот интерфейс для использования реализации Async:
container.RegisterManyForOpenGeneric(typeof(IHandleCommands<>),
(type, implementations) =>
{
// register the async email handler
if (type == typeof(IHandleCommands))
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. для всех других реализаций вместо того, чтобы явно вызывать их в обратном вызове.
Обновить ответ на ответ Стивена
Я обновил свой вопрос, чтобы он был более точным. Причина, по которой я реализовал это таким образом, заключается в том, что в рамках операции команда обновляет свойство System.Nullable
с именем SentOnUtc
в объекте базы данных после MailMessage
успешно отправлено.
ICommandEntities
и IUnitOfWork
реализуются инфраструктурой сущностей классом DbContext
. DbContext
регистрируется в контексте http с использованием метод, описанный здесь:
container.RegisterPerWebRequest();
container.Register(container.GetInstance);
container.Register(container.GetInstance);
container.Register(container.GetInstance);
Поведение по умолчанию метода расширения RegisterPerWebRequest
в вики simpleinjector заключается в регистрации переходного экземпляра, когда 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-запроса.