rebus-org / Rebus.ServiceProvider

:bus: Microsoft Extensions Dependency Injection container adapter for Rebus
https://mookid.dk/category/rebus
Other
66 stars 32 forks source link

Allow injecting Scoped ServiceProvider. #60

Closed EraYaN closed 2 years ago

EraYaN commented 2 years ago

This should allow for for example the Rebus.Events package to get an actual usable Scope (the same as in the handler). So we can use it to process headers on both incoming and outgoing messages.


Rebus is MIT-licensed. The code submitted in this pull request needs to carry the MIT license too. By leaving this text in, I hereby acknowledge that the code submitted in the pull request has the MIT license and can be merged with the Rebus codebase.

CLAassistant commented 2 years ago

CLA assistant check
All committers have signed the CLA.

mookid8000 commented 2 years ago

Hi @EraYaN , thanks for contributing 🙂 could you explain to me what problem this PR solves?

EraYaN commented 2 years ago

Well the root provider can not resolved Scoped services (anything that would normally be available on a per request basis). And this extra bit of configuration lets you access those services as well in pipeline steps before the actual handler (so you can do things to them before the handler starts).

In our case, we have to inject our user context (from message headers) into some Scoped services and we use the Events package, hence my PR there) for that (or some other custom step). We need the same scope for the whole request as it were, so that is why this extension should inject (optionally) a scoped ServiceProvider.

Scoped sits in between Singleton (one per application) and Transient (one per resolve request) and is very useful. Also anything EF Core with a DbContext is always scoped, so if you want to do anything with a database in pipeline steps for example this is also mandatory.

mookid8000 commented 2 years ago

OK, maybe I don't understand this completely, but if I do, I don't think this is necessary. 🙂

The current implementation of the handler activator DependencyInjectionHandlerActivator only creates a scope if it cannot find an IServiceScope registered already. Check it out here: https://github.com/rebus-org/Rebus.ServiceProvider/blob/master/Rebus.ServiceProvider/ServiceProvider/DependencyInjectionHandlerActivator.cs#L52-L67

This means that any step executed before that (but after ServiceProviderProviderStep) can just get the IServiceProvider from the incoming context and create a scope with it and stash it in the context, which in turn will cause the handler activator to resolve handlers from there.

Is there something I am missing here?

EraYaN commented 2 years ago

Well the whole point was to make this step (the one from this package: ServiceProviderProviderStep) do it since it injects the ServiceProvider anyway, why not make it also do the scope creation? Instead of having to add another step, which in my mind wouldn't result in a better design at all. I could also remake this so that the ScopeInjection step is something separate, but I feel I'd be a lot quicker to just have it done correctly in the first place.

mookid8000 commented 2 years ago

(..) why not make it also do the scope creation?

It's made this way because it makes it possible for you to provide the scope, if you like.

EraYaN commented 2 years ago

It's a bit of a pain that to provide said scope you essentially re-implement that part of this package. We'll just maintain the fork for now.

mookid8000 commented 2 years ago

@EraYaN check this out 👉 https://github.com/rebus-org/Rebus.ServiceProvider/blob/master/Rebus.ServiceProvider.Tests/Examples/ProvideCustomServiceScope.cs#L67-L98

It's an example on how an incoming pipeline step can be injected to manage a custom scope. 🙂

Would that do the trick for you? Or is there something I have missed?

EraYaN commented 2 years ago

I might have a look at something like that, but that just sort of reimplements the ServiceProviderProviderStep that this package already has right? Just with the extra feature of making a scope. Hence why I figured to add the functionality to the existing step, Especially since a scoped service provider has all features of the root service provider for all most all injection scenarios (essentially the thing it can't do is create scopes directly on the root for obvious reasons) so for us there is no reason to have both separately.