Search code examples
c#dependency-injection.net-coresimple-injectormasstransit

DI woes with Mass Transit consumer and Simple Injector


I have a .net core web api which I have been trying to add a Mass Transit consumer to. The consumer has a dependency on a service. The service is registered with the Simple Injector container.

container.Register<IMyService, MyService>(Lifestyle.Scoped); 

The service in turn has a dependency on a DBContext which was added using the IServiceCollection.AddDbContext and then has been cross wired thus :

Container.CrossWire<MyDbContext>(app);  

I have added the service to the constructor of the consumer like so :

public AddMyCommandConsumer(IMyService service)
{
    _service = service;                
}

So my next step was to setup the end point. First try thus :

cfg.ReceiveEndpoint(host, options.MyQueue, e =>
{
    var service = container.GetService<IMyService>();
    e.Consumer(() => new AddMyCommandConsumer(service));
}

Failed dismally due to the fact that the service is Async scoped. Simple Injector didn't like that. So after a bit of digging, I came up with a second iteration :

cfg.ReceiveEndpoint(host, options.MyQueue, e =>
{
    using (AsyncScopedLifestyle.BeginScope(container))
    {
        var service = container.GetService<IMyService>();
        e.Consumer(() => new AddMyCommandConsumer(service));
    }
}

This get's a bit further, with the consumer being created, and the consume method being called, but as soon as I use the service and the DBContext is needed, simple injector complains that the DBContext has been disposed. If I remove the using statement then it works, but the scope is never disposed.

I then had a look at the MassTransit.SimpleInjector nuget package. There is no documentation and no examples that I can find, but after a bit of digging I came up with this :

var consumerFactory = new ScopeConsumerFactory<AddMyCommandConsumer>(new 
    SimpleInjectorConsumerScopeProvider(container));                        
e.Consumer(consumerFactory);

Mass Transit attempts to create the consumer, but this time it fails as Simple Injector is unable to create the service even though it is registered in the container. This last attempt is a little bit of a guess as I said above, there is no documentation or examples. To be honest I think even if I got this working, I'd end up with the same problem with the DBContext as above.

Any ideas?


Solution

  • Add a reference to the MassTransit.SimpleInjector package, and then in your receive endpoint, configure the consumer using:

    cfg.ReceiveEndpoint("queue_name", e =>
    {
        e.Consumer<AddMyCommandConsumer>(container);
    });
    

    The extension method will properly configure the scope provider and add the consumer factory to pull from the container. Your consumer should be registered as AsyncScoped, so that it properly handles async/await (MT is heavily built on the TPL).

    container.Register<AddMyCommandConsumer>(Lifestyle.Scoped);
    

    Also, I think there is a container option to specify async lifestyle as the default.

    container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
    

    Hopefully this helps, any other error you get is likely related to how your service or DbContext are registered.