Как настроить Simple Injector для запуска фоновых потоков в ASP.NET MVC

Я использую Simple Injector для управления временем жизни моих внедренных зависимостей (в данном случае UnitOfWork), и я очень доволен тем, что у меня есть отдельный декоратор, а не чем моя служба или обработчик команд, следящий за сохранением и удалением, делает код намного проще при написании слоев бизнес-логики (я следую архитектуре, описанной в этом сообщении в блоге).

Вышеупомянутое работает отлично (и очень легко) с использованием пакета Simple Injector MVC NuGet и следующего кода во время построения корневого контейнера композиции, если в графе существует более одной зависимости, один и тот же экземпляр внедряется во все - идеально подходит для контекста модели Entity Framework.

private static void InitializeContainer(Container container)
{
    container.RegisterPerWebRequest();
    // register all other interfaces with:
    // container.Register();
}

Теперь мне нужно запустить несколько фоновых потоков и понять из документации Simple Injector по потокам, что команды могут быть проксированы следующим образом:

public sealed class TransactionCommandHandlerDecorator
    : ICommandHandler
{
    private readonly ICommandHandler handlerToCall;
    private readonly IUnitOfWork unitOfWork;

    public TransactionCommandHandlerDecorator(
        IUnitOfWork unitOfWork, 
        ICommandHandler decorated)
    {
        this.handlerToCall = decorated;
        this.unitOfWork = unitOfWork;
    }

    public void Handle(TCommand command)
    {
         this.handlerToCall.Handle(command);
         unitOfWork.Save();
    }
}

ThreadedCommandHandlerProxy:

public class ThreadedCommandHandlerProxy
    : ICommandHandler
{
    Func> instanceCreator;

    public ThreadedCommandHandlerProxy(
        Func> creator)
    {
        this.instanceCreator = creator;
    }

    public void Handle(TCommand command)
    {
        Task.Factory.StartNew(() =>
        {
            var handler = this.instanceCreator();
            handler.Handle(command);
        });
    }
} 

см., что фабрики используются, если я добавлю фабрики к своим командам, а уровень обслуживания будет запутан и непоследовательен, поскольку у меня будут разные методологии сохранения для разных служб (один контейнер обрабатывает сохранение, другие созданные экземпляры фабрик в службах обрабатывают сохранение и удаление) - вы можете посмотрите, насколько понятен и прост скелет кода службы без каких-либо фабрик:

public class BusinessUnitCommandHandlers :
    ICommandHandler,
    ICommandHandler
{
    private IBusinessUnitService businessUnitService;
    private IInvoiceService invoiceService;

    public BusinessUnitCommandHandlers(
        IBusinessUnitService businessUnitService, 
        IInvoiceService invoiceService)
    {
        this.businessUnitService = businessUnitService;
        this.invoiceService = invoiceService;
    }

    public void Handle(AddBusinessUnitCommand command)
    {
        businessUnitService.AddCompany(command.name);
    }

    public void Handle(DeleteBusinessUnitCommand command)
    {
        invoiceService.DeleteAllInvoicesForCompany(command.ID);
        businessUnitService.DeleteCompany(command.ID);
    }
}

public class BusinessUnitService : IBusinessUnitService
{
    private readonly IUnitOfWork unitOfWork;
    private readonly ILogger logger;

    public BusinessUnitService(IUnitOfWork unitOfWork, 
        ILogger logger)
    {
        this.unitOfWork = unitOfWork;
        this.logger = logger;
    }

    void IBusinessUnitService.AddCompany(string name)
    {
        // snip... let container call IUnitOfWork.Save()
    }

    void IBusinessUnitService.DeleteCompany(int ID)
    {
        // snip... let container call IUnitOfWork.Save()
    }
}

public class InvoiceService : IInvoiceService
{
    private readonly IUnitOfWork unitOfWork;
    private readonly ILogger logger;

    public BusinessUnitService(IUnitOfWork unitOfWork, 
        ILogger logger)
    {
        this.unitOfWork = unitOfWork;
        this.logger = logger;
    }

    void IInvoiceService.DeleteAllInvoicesForCompany(int ID)
    {
        // snip... let container call IUnitOfWork.Save()
    }
}

С вышеизложенным моя проблема начинает формироваться, как я понимаю из документации по времени жизни ASP .NET PerWebRequest, используется следующий код :

public T GetInstance()
{
    var context = HttpContext.Current;

    if (context == null)
    {
        // No HttpContext: Let's create a transient object.
        return this.instanceCreator();
    }

    object key = this.GetType();
    T instance = (T)context.Items[key];

    if (instance == null)
    {
        context.Items[key] = instance = this.instanceCreator();
    }
    return instance;
}

Приведенное выше работает нормально, для каждого HTTP-запроса будет действительный HttpContext.Current, однако, если я запускаю новый поток с помощью ThreadedCommandHandlerProxyон создаст новый поток, и HttpContextбольше не будет существовать в этом потоке.

Поскольку HttpContextбудет иметь значение null при каждом последующем вызове, все экземпляры объектов, внедренные в конструкторы сервисов, будут новыми и уникальными, в отличие от обычного HTTP для каждого веб-запроса, где объекты совместно используются правильно как одни и те же. во всех службах.

Итак, резюмируя вышеизложенное в виде вопросов:

Как мне получить сконструированные объекты и внедрить общие элементы независимо от того, созданы ли они из HTTP-запроса или через новый поток?

Существуют ли какие-либо особые соображения относительно UnitOfWork, управляемого потоком в прокси-сервере обработчика команд? Как можно обеспечить его сохранение и удаление после выполнения обработчика?

Если бы у нас возникла проблема на уровне обработчика команд/уровня службы и мы не хотели бы сохранять UnitOfWork, мы бы просто выдали исключение? Если да, то можно ли поймать это на глобальном уровне или нам нужно перехватывать исключение для каждого запроса из try- catchв декораторе обработчика или прокси?

Спасибо,

Крис

8
задан Tsahi Asher 15 October 2015 в 07:35
поделиться