3

I'm trying to use a Scoped dependency from a NServiceBus Behavior.

From NServiceBus Behavior docs:

Behaviors are only created once and the same instance is reused on every invocation of the pipeline. Consequently, every behavior dependency will also behave as a singleton, even if a different option was specified when registering it in dependency injection. Furthermore, the behavior, and all dependencies called during the invocation phase, need to be concurrency safe and possibly stateless. Storing state in a behavior instance should be avoided since it will cause the state to be shared across all message handling sessions. This could lead to unwanted side effects.

Since a Behavior is a Singleton and the Invoke method of the Behavior doesn't allow to inject any dependency (such as the invoke method of a net core middleware because in this case it's a regular interface implementation), I can't use a scoped dependency from here.

I've tried to resolve my dependencies in my Invoke method for each incoming/outgoing message by passing IServiceCollection in the constructor:

private readonly IServiceCollection _services;

public MyIncomingMessageBehavior(IServiceCollection services)
{
    _services = services;
}

public override async Task Invoke(IIncomingLogicalMessageContext context, Func<Task> next)
{
    var myScopedDependency = _services.BuildServiceProvider().GetService<IMyScopedDependency>();
    // always 
}

But this doesn't work:

That's because when you inject IServiceProvider into your middleware - that's "global" provider, not request-scoped. There is no request when your middleware constructor is invoked (middleware is created once at startup), so it cannot be request-scoped container.

In summary, my scoped dependency contains data for the current context and I'd like to access this data from the Invoke method of my Behavior singleton?

Is there any way to do it?

Alpha75
  • 2,140
  • 1
  • 26
  • 49

1 Answers1

6

You need to create a scope before resolving your dependency:

private readonly IServiceScopeFactory _scopeFactory;

public MyIncomingMessageBehavior(IServiceScopeFactory scopeFactory)
{
    _scopeFactory = scopeFactory;
}

public override async Task Invoke(IIncomingLogicalMessageContext context, Func<Task> next)
{
    using(var scope = _scopeFactory.CreateScope())
    {
        var myScopedDependency = scope.ServiceProvider.GetService<IMyScopedDependency>();
    }
}

Also, pay attention that your dependency is disposed along with scope.

Alex Riabov
  • 8,655
  • 5
  • 47
  • 48
  • 1
    I don't think it is wise to build a new service provider one _each call_ to the middleware. This means you are creating a new container on _every_ incoming message. The creation of a service provider, however, has a performance penalty, just as the first resolve of a certain dependency has. On top of that, every service provider has its own set of singleton instances, causing registered singletons to behave as _scoped_ dependencies, which is typically not what you want. – Steven Oct 02 '18 at 10:20
  • 1
    @Steven, you're right, I've updated the answer, now there shouldn't be that issue – Alex Riabov Oct 02 '18 at 10:28
  • Thumbs up and +1 from me. – Steven Oct 02 '18 at 10:29
  • 1
    Although it can be implied, it would be good, though, to make clear in the example that the `IMyScopedDependency` dependency is used within that scope, because it will be released/decomposed when the `using` block ends. – Steven Oct 02 '18 at 10:31
  • This creates a scope for my invoke method. I don't want to stablish a new scope here, I want to access/resolve a scoped dependency that it was created for the current HttpContext. This HttpContext sends a new message and I expected to access to a service of this scope. I hope I have explained well. – Alpha75 Oct 02 '18 at 12:25
  • 2
    @Alpha75 I'm afraid, Behavior lives out of your request scope, so it is not possible to resolve the scoped dependency with value that was created for HttpContext. – Alex Riabov Oct 02 '18 at 13:02
  • Finally I will move my data from one context to other using headers when I send or publish. Maybe I could decorate IMessageSession to pass always my HttpContext headers to NServiceBus headers. However, I've created the scoped dependency that you have proposed to maintain certain data to be thread safe. – Alpha75 Oct 11 '18 at 14:51