-
Notifications
You must be signed in to change notification settings - Fork 314
Conversation
So this expands the check from just Scoped dependencies to a lifetime check. I'm open to making this a completely separate check based on a separate boolean in configuration as well. |
Even though this highlights potential bugs/leaks in your code, and the scope checking isn't enabled by default outside the |
@khellang correct, so maybe adding this as a separate option is a better idea as that would make it non-breaking |
Looks like it might've just been overlooked/forgotten... #430 |
I guess it could be kept under the same flag, but using something similar to MVC's CompatibilitySwitch. Adding flags to methods gets really messy quickly. |
Capturing transitive from singletons/scopes is often completely valid scenario, this would cause too many false positives. |
@pakrym how is that the case? That is exactly the behavior that you're trying to prevent by setting the ValidateScopes flag to true? A transitive in a singleton becomes a singleton. |
Often transitive is not more then "I want a new instance every time because I have internal state and don't care about how long my object would be alive". Also, this change would make transitive useless in ASP.NET Core, because you are always in request scope. |
So if the abstraction is that you get a new instance every single time, how does that hold if you're resolving a transient dependency inside a singleton? Are you saying that every dependency in my controller has request scope? |
It still holds you resolved one singleton instance you got one dependency instance. If in the controller you call |
Ok so let's assume that as the implementor of an interface, I do not know who takes a dependency on me (as it should) and I would like to use internal state. How do I guarantee that one of my consumers does not make me a singleton scoped instance? Take this below code for example, when I'm developing ITransientDependency I am making the assumption that I will be instantiated every single time. Isn't this exactly what the developer of for example IOptionsSnapshot assumes?
|
Why would you care about this? |
Let me turn the question around, why do you care about a singleton taking a dependency on a scoped dependency? |
Because we've seen bugs caused by it and it wasn't aggressively breaking a lot of code. Also transient does not denote how "long" the services would live just that it "Transient lifetime services are created each time they're requested. " There was a discussion about capturing IDisposible instances in root container: #456 Did you try running MVC app with this checks enabled? |
I did, it doesn't fail and it increases the count with every request. Here's my program.cs:
and startup
|
Strange. There are a lot of MVC singleton services that cache transients: |
So the issue here is that nesting a transient in a singleton does not fail. So singleton > scoped > transient (in lifetime) |
@hvanbakel Did you try running MVC with your additional check enabled? |
I did, so now I get:
which is good. Because this signals that It's a bit odd to crash for singleton wrapping a scoped but not for this though as it is essentially the same problem of 1 component with a long lifetime wrapping a component with a short lifetime. |
Captive dependency checking checked for singletons being dependent upon a scoped dependency. However, depending on a dependency which lifetime is shorter than its parent is not specific to that relation. This PR adds checks for Singleton -> Transient and Scoped -> Transient.