Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DbContext creation into message handler

Tags:

I have a dll wich expose a type like

public class MyDbContext {
    [...]
}

inside this library I also have an IPackage implementation which register the MyDbContext in the container like

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

This assembly is then referenced from two different types of applications: - a web api project - an asp.net mvc application

This is the initialization of the web api project

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

and this is the initialization of the mvc application

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

When I receive a message from a Rebus queue (in the mvc application) the container try to instatiate a message handler like this

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

but I receive an error saying

Rebus.Retry.ErrorTracking.InMemErrorTracker - Unhandled exception 1 while handling message with ID 85feff07-01a6-4195-8deb-7c8f1b62ecc3: SimpleInjector.ActivationException: The MyDbContext is registered as 'Web Request' lifestyle, but the instance is requested outside the context of an active (Web Request) scope.

with the following stack trace

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]()

I have also tried to setup the Async Scoped Lifestyle in the mvc application but the error is substantially the same.

like image 678
Lorenzo Avatar asked Apr 20 '17 11:04

Lorenzo


1 Answers

Rebus runs your handlers on a background thread, not on a web request thread. This means that it is impossible to use the WebRequestLifestyle as part of the Rebus pipeline.

You should make sure that an async scope is explicitly started before a handler is executed. You can do that with a decorator/proxy.

On top of that, you should use a hybrid lifestyle for your MVC application, because MVC uses WebRequestLifestyle instead of `AsyncScopedLifestyle.

You can apply your hybrid lifestyle in your MVC application as follows:

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

Your decorator should look as follows:

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);
        }
    }
}

You can register your decorator as follows:

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

You should register this decorator both in MVC and your Web API project.

like image 91
Steven Avatar answered Sep 23 '22 10:09

Steven