Service provider returns the same instance even if the component is registered with a scoped lifecycle
Same instance is shared between all requests, eliminating the purpose of per request lifecycle.
A component with a scoped lifecycle is injected to a component with a singleton lifecycle via a factory method. Factory method is registered as a delegate to resolve the instance from the container every time.
Press F5 to send a request to ValuesController and display the counter values.
Returned results should not have increased, but should have remained same for every new request.
Counter keeps increasing with every request.
If we use the transient lifecycle, we can see that the factory creates a new instance and returns the first counter value. This makes me believe that there is nothing wrong with the factory delegate approach.
It's possible that the scope is captured with a closure in the overloads with implementationFactory delegate(wild guess).
What you're getting is expected. The IRequestService is a singleton which resolves a scoped service (Func<IRequestContext>) which means that the Func you captured closes over the top level service provider instead of the scoped one. In fact, if you update your sample to use 1.1.0 and call use the new feature that allows you to detect singletons depending on scoped services (captive dependencies), you'll see this error:
System.InvalidOperationException: Cannot consume scoped service 'System.Func`1[DependencyInjection.AddScopedBug.Controllers.IRequestContext]' from singleton 'DependencyInjection.AddScopedBug.Controllers.IRequestService'.
IRequestService can't be a singleton.
What do you mean by "Func you captured closes over the top level service provider"? Why doesn't it close over a transient object? Func
I am aware of the problem you mentioned. The whole point is not to force scoped life cycle for every object higher in the graph. This approach works with other DI containers, it would in this DI container as well if I were to use transient life cycle.
Anyway is there a possible workaround? If there isn't one, would you be interested in supporting this? I could work on it and send a PR.
What do you mean by "Func you captured closes over the top level service provider"? Why doesn't it close over a transient object? Func has no knowledge of the object that's higher in the graph.
Here's a bare bones example of what you did:
```C#
public class Singleton
{
private Func
public Singleton(IServiceProvider serviceProvider)
{
_factory = serviceProvider.GetRequiredService<Scoped>;
}
}
public class Scoped
{
}
```
You captured the "non scoped" IServiceProvider in the singleton object graph.
This approach works with other DI containers, it would in this DI container as well if I were to use transient life cycle.
Those containers have first class support for Func<T> similar to how we have support for IEnumerable<T>. This one doesn't support Func<T> so you're running into the expected behavior here.
Anyway is there a possible workaround?
Make IRequestService scoped.
If there isn't one, would you be interested in supporting this? I could work on it and send a PR.
We've been reluctant to add any more features to the DI container that aren't supported by most of the DI containers people want to use with ASP.NET Core. So I'd say, at the moment no. If it turns out the masses support Func<T> natively then we can take another look again but that requires a bit of research.
Thanks for the explanation David. IEnumerable<T> support is really great, wish we had Func<T> as well.
@uhaciogullari I'm also interested in this. But I don't have a captive dependency issue like you did.
I emulated Func<T> behavior by using
services.AddScoped<Func<TFoo>>(sp => sp.GetRequiredService<TFoo>);
I log each instance's foo.GetHashCode(); They should be different, but they're always equal. I also set a breakpoint on that line, and it's only hit the first time.
I suspect this "trick" doesn't work, at all.
Please confirm whether this trick works for you? I think it doesn't. Do you have a workaround (other than using a third-party container)?
@grokky1 Sorry for replying late. Nope, the workaround doesn't work.
Most helpful comment
Thanks for the explanation David.
IEnumerable<T>support is really great, wish we hadFunc<T>as well.