Simpleinjector: правильный ли это способ RegisterManyForOpenGeneric, когда у меня есть 2 реализации и я хочу выбрать одну?

Использование простого инжектора с шаблоном команды, описанным здесьи шаблоном запроса, описанным здесь. Для одной из команд у меня есть 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-запроса.

5
задан Steven 11 August 2014 в 11:35
поделиться