I'm having a huge problem with the configuration/dependency injection of an application.
I have a singleton class added through DI with AddSingleton, that has in its constructor a IRequestClient, that is scoped because
busConfigurator.AddRequestClient()
which among other things, has the same effect as AddScoped.
When I start the app, it says "Cannot consume scoped service 'MassTransit.IRequestClient`1[...]' from singleton '...'.)"
Which absolutely makes sense.
The weirdest thing is that I have another app set up the exact same way, but it just works and I would really like for that class to remain singleton.
My colleague and I spent an entire day trying to find the differences between the two applications, but they are virtually the same in their configurations, so we are having trouble in understanding why one works while the other doesn't.
I'm not entirely sure on what details could be important to better define the problem, so feel free to ask.
We've looked all around the internet trying to find a solution, but it was always "Change singleton to transient", but that's not an option, first because it HAS to be a singleton, otherwise it wouldn't make sense in our app, as that thing is what caches lots of date from our db so we can't just go around keeping on collecting heaps of data, second because the first app works with singleton, not with transient and we'd like to keep it that way
CodePudding user response:
When a dependency is scoped, the implication is that a new instance is needed for each scope (which is usually an incoming HTTP request or message.) It implies that the instance should not be re-used for multiple requests.
If you have a singleton that depends on that scoped dependency, that singleton will be created using an instance of that dependency (the request client.) Because that singleton "lives forever," so does the instance of the request client it contains.
The result is that the request client is not supposed to be re-used across different scopes, but now it is. One instance is used forever.
A likely solution is to modify the class that depends on that client so that it doesn't need to be a singleton. You mentioned that it has to be a singleton because it caches data.
How does it cache data? Does it do so by storing data in a private field? If so, perhaps you could make that field static. Now the class instance isn't re-used, but those fields are shared between instances. (Verify that interaction with those fields is thread safe if they may be accessed concurrently.)
Or if there's some other cache mechanism, you could move that into its own dependency and make that a singleton.
Then your class can be scoped. It will depend on the singleton cache, always using the same instance. It will also depend on the scoped request client, using a new instance for each scope.
CodePudding user response:
You could inject IServiceProvider
instead, and create a scope when the singleton needs to perform a request. That way, you're sticking to the expected use of the request client.
await using var scope = provider.CreateAsyncScope();
var client = scope.ServiceProvider.GetRequiredService<IRequestClient<T>>();
await client.GetResponse(...);