Closed arielmoraes closed 6 years ago
How can I inject scoped services into Events or Pipeline Steps?
Rebus does not use your IoC container (ServiceProvider in this case) to resolve anything but your message handlers.
This is very deliberate, because there's simply too many subtle differences between container implementations to be able to make that work reliably. Also, pretty much all of Rebus' services are singletons, so it's not that interesting to have them resolved in an IoC container.
If you want to inject something into your own pipeline step, you need to inject it when the step is created, which is most likely during the registration:
Configure.With(...)
.(...)
.Options(o => {
o.YourCustomConfigurationThingHere();
})
public static void YourCustomConfigurationThingHere(this OptionsConfigurer configurer)
{
configurer.Decorate<IPipeline>(c =>
{
var pipeline = c.Get<IPipeline>();
var step = new YourOwnPipelineStep();
return new PipelineStepInjector(pipeline)
.OnSend(step, PipelineRelativePosition.Before, typeof(SerializeOutgoingMessageStep));
});
}
So, if you want to have something resolved for each message, I suggest you pass a factory method into the YourOwnPipelineStep
ctor, and then you resolve/release as appropriate.
Let me explain a little more, we have a scenario where we have a multitenancy and one database per tenant, to know which tenant the message is for we've added a header in the message with the tenant id. We have created an ITenantAccessor to get the tenant for the current message being handled so that when we need to create our DbContext we know which Database we should point to. So as this is per message the ITenantAccessor was registered as Scoped. Any tips on how to achieve that when using Rebus?
Which IoC container are you using? Windsor?
I'm using the .NET Core Native IoC and the Rebus.ServiceProvider library.
aaaahahahahaha, sorry!! I must not be completely awake 😁
You:
If you use the Rebus.ServiceProvider library (...)
Me:
Are you using Windsor?
Sorry about that 👍
What does it mean for a registration to be "scoped" with ServiceProvider?
hahaha no problem.
When you call ServiceProvider.CreateScope()
it will create a new scope, if you have a service called MyService registered with the container all calls to GetService<MyService>
using the created scope will return the same instance. To understand it easily you can imagine it as "Instance per request" when using the ASP.NET Core.
Well in that case, you should probably wrap the incoming pipeline in your scope somehow. You could probably get the most of the way by creating a nifty extensions method like this:
public static void EnableServiceProviderScopeThing(this OptionsConfigurer configurer)
{
configurer.Decorate<IPipeline>(c =>
{
var pipeline = c.Get<IPipeline>();
var step = new ServiceProviderScopeStep();
return new PipelineStepInjector(pipeline)
.OnReceive(step, PipelineRelativePosition.After, typeof(DeserializeIncomingMessageStep));
});
}
and then enable it like this:
Configure.With(...)
.(...)
.Options(o => {
o.EnableServiceProviderScopeThing();
})
and then your ServiceProviderScopeStep
could look somewhat like this:
public class ServiceProviderScopeStep : IIncomingStep
{
public async Task Process(IncomingStepContext context, Func<Task> next)
{
using(var scope = ServiceProvider.CreateScope())
{
await next();
}
}
}
After creating the scope, it might be necessary to stash it in the IncomingStepContext
somewhere, and then retrieve it again when you resolve your handlers. Not sure how ServiceProvider handles that part?
Actually I needed to have access to the scope throughout the lifetime of a Message. I ended up creating my own ContainerAdapter and when the time comes to get the Handlers I add the created scope to the Transaction Context
.
So instead of using the Container Adapter provided by the Rebus.ServiceProvider package now I use the following:
public Task<IEnumerable<IHandleMessages<TMessage>>> GetHandlers<TMessage>(TMessage message, ITransactionContext transactionContext)
{
var scope = _provider.CreateScope();
var resolvedHandlerInstances = GetMessageHandlersForMessage<TMessage>(scope);
transactionContext.Items.TryAdd("CurrentScope", scope);
transactionContext.OnDisposed(scope.Dispose);
return Task.FromResult((IEnumerable<IHandleMessages<TMessage>>)resolvedHandlerInstances.ToArray());
}
So whenever I need to access that scope to retrieve a service outside the flow of the Handlers I access the current Transaction Context
.
That's a pretty good solution, much better than mine actually 😄
I see now that I was aiming for a "cross-the-river-to-get-water"-kind of solution(*) – it's much easier to simply create the scope and hook up with the transaction context's callbacks in the container adapter.
(*) It's a Danish saying that you should never cross the river to get water 😉
We also have that saying here in Brazil hahaha, thanks for the tips once again.
With Simple Injector, it worked perfectly, I didn't have to create my own container adapter.
If you use the Rebus.ServiceProvider library it will register the IBus as a Singleton, and as the Events are fired in the Singleton context it is not possible to get a Scoped service inside the event in .NET Core. The exception message is
'Cannot resolve scoped service 'ServiceName' from root provider.'
I tried to add a new Pipeline Step but there is no injection available on it. How can I inject scoped services into Events or Pipeline Steps?