Создание DbContext в обработчик сообщений

У меня есть DLL, которая выставляет тип как

public class MyDbContext {
    [...]
}

внутри этой библиотеки у меня также есть IPackage реализация, которая регистрирует MyDbContext в контейнере как

public void RegisterServices( Container container ) {
    container.Register<MyDbContext>( Lifestyle.Scoped );
}

Затем на эту сборку ссылаются из двух разных типов приложений: - проект web api - приложение asp.net mvc

Это инициализация проекта веб-API

var container = new Container();
container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
InitializeContainer( container );
container.RegisterWebApiControllers( GlobalConfiguration.Configuration );
container.Verify();

и это инициализация приложения MVC

var container = new Container();
container.Options.DefaultScopedLifestyle = new WebRequestLifestyle();
InitializeContainer( container );
container.RegisterMvcControllers( Assembly.GetExecutingAssembly() );
container.Verify();

Когда я получаю сообщение из очереди Rebus (в приложении mvc), контейнер пытается установить обработчик сообщений, подобный этому

public class MyHandler : BaseMessageHandler, IHandleMessages<MyMessage>, IHandleMessages<IFailed<MyMessage>>
{
    public MyHandler( ILog logger, MyDbContext context ) {
        _logger = logger;
        _context = context;
    }
}

но я получаю сообщение об ошибке

Rebus.Retry.ErrorTracking.InMemErrorTracker - необработанное исключение 1 при обработке сообщения с идентификатором 85feff07-01a6-4195-8deb-7c8f1b62ecc3: SimpleInjector.ActivationException: MyDbContext зарегистрирован как стиль жизни веб-запроса, но экземпляр запрашивается вне контекста активная область (веб-запрос).

со следующей трассировкой стека

at SimpleInjector.Scope.GetScopelessInstance[TImplementation](ScopedRegistration`1 registration)
at SimpleInjector.Scope.GetInstance[TImplementation](ScopedRegistration`1 registration, Scope scope)
at SimpleInjector.Advanced.Internal.LazyScopedRegistration`1.GetInstance(Scope scope)
at lambda_method(Closure )
at SimpleInjector.InstanceProducer.GetInstance()
at SimpleInjector.Container.GetInstance[TService]()

Я также попытался настроить Async Scoped Lifestyle в приложении mvc, но ошибка по сути та же.

1 ответ

Решение

Ребус запускает ваши обработчики в фоновом потоке, а не в потоке веб-запросов. Это означает, что невозможно использовать WebRequestLifestyle как часть трубопровода Ребус.

Вы должны убедиться, что асинхронная область явно запущена перед выполнением обработчика. Вы можете сделать это с декоратором / прокси.

Кроме того, вы должны использовать гибридный образ жизни для вашего приложения MVC, потому что MVC использует WebRequestLifestyle вместо AsyncScopedLifestyle.

Вы можете применить свой гибридный образ жизни в приложении MVC следующим образом:

container.Options.DefaultScopedLifestyle = Lifestyle.CreateHybrid(
    defaultLifestyle: new AsyncScopedLifestyle(),
    fallbackLifestyle: new WebRequestLifestyle());

Ваш декоратор должен выглядеть следующим образом:

public sealed class AsyncScopedMessageHandler<T> : IHandleMessages<T>
{
    private readonly Container container;
    private readonly Func<IHandleMessages<T>> decorateeFactory;
    public AsyncScopedMessageHandler(Container container, Func<IHandleMessages<T>> factory)
    {
        this.container = container;
        this.decorateeFactory = factory;
    }
    public async Task Handle(T message) {
        using (AsyncScopedLifestyle.BeginScope(this.container)) {
            var decoratee = this.decorateeFactory.Invoke();
            await decoratee.Handle(message);
        }
    }
}

Вы можете зарегистрировать свой декоратор следующим образом:

container.RegisterDecorator(
    typeof(IHandleMessages<>),
    typeof(AsyncScopedMessageHandler<>),
    Lifestyle.Singleton);

Вы должны зарегистрировать этот декоратор как в MVC, так и в вашем проекте Web API.

Другие вопросы по тегам