-
Notifications
You must be signed in to change notification settings - Fork 152
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow disabling resolving concrete types #377
Comments
I completely vote for disabling this by default. I shot myself in my foot multiple times because concrete types where autowired. One scenario that comes to mind is where some concrete service is used in a transient component at one part of the solution, but this same component is scoped or singleton in an other part of the solution. Although Simple Injector will prevent this other application from running when this happens and the exception message is super clear it is most of the times still a surprise why the container won't verify for this application because in my mind nothing has really changed in this application, while in fact something has changed. And I think it is just sane practice the configure all parts of the application. |
I completely agree with disabling this by default. With option to enable when needed. Never had to use this function because CQRS. |
I'm really interested to see some examples from developers who are actually currently depending on this behavior. |
Perhaps concrete types could be resolvable if they implement no interfaces? I have in the past used simple 'record' types for providing strongly-typed access to config settings, for example |
@alexfoxgrill, but wouldn't that require those records to be registered as singleton? |
I don't see how the lifestyle is impacted by this..? You could just as easily have transient instances if you wanted? |
When you have objects that provide strongly typed access to configuration values, these configuration values should typically be loaded at app startup to ensure the application fails fast in case of a misconfiguration. In case config values are read once at startup, it means that these strongly typed wrappers just wrap the values and are created at startup as well. This means they will be singletons as well. What you are describing seems an object that contains behavior and forwards the request to something like The case where this does make a lot of sense is when the comfig values are volatile and can change during the lifetime of the application. |
I think there are different options here when it comes to resolving unregistered concrete types:
The dilemma is here that although we want to improve Simple Injector, we also want to prevent introducing breaking changes that will impact and confuse too many users, and potentionally cost a lot of effort to fix, while they might have a valid use case for this. I'm currently thinking about making the "don't resolve unregistere concrete root types" the default for v4 (although that allone could be quite a breaking change already), where the user might be able to switch to "Resolve All" and "Resolve None" using the |
After building a proof of concept, a massive amount of (218) tests broke. Main reason for this is that a lot of tests depend on the behavior to be able to resolve unregistered concrete types. But also some of the current code samples in the documentation depend on this behavior (like the interception extensions). This makes me realize that, although, this change might be a noble one, it might impact more users than I first envisions and it is quite some work to get this feature in correctly. I am, therefore, going to postpone adding this configuration switch till after v4, and if the default behavior (which is to resolve all unregistered concrete types) is changed, this will be done in the following major release, v5. Thanks for everyone responding. |
Passed along InjectionConsumerInfo more often during the resolving phase. This makes it easier to add a feature like #377 in the future.
@dotnetjunkie An example which comes to my mind where this feature causes confusion is when using Entity Framework. If I don't want to register my custom Say for example you have a context named // Injects MyCustomContext => everything ok
public MyClass(MyCustomContext ctx) { }
public void SampleMethod() => ctx.Set<MyEntity>(); // ok However, in another class, you accidentally used // Injects DbContext which does not makes sense and fails later on
public MyClass2(DbContext ctx) { }
// fails because DbContext has no registrations
public void SampleMethod() => ctx.Set<MyEntity>(); Of course, you can easily fix this by registering
but this only works as long as there is a single => I`d prefer to disable resolving unregistered concrete types per default. A switch to enable it would make sense though. |
Is there any way currently to disable the automatic resolution of concrete types? In my environment this can easily lead to bugs, so it would be nice if there is a way to force registration of these types. How about introducing the property: Is there any other way to disable the behavior in the current release? |
The way to disable this in the current release is by adding the following code to the container: container.ResolveUnregisteredType += (s, e) =>
{
if (!e.Handled && !e.UnregisteredServiceType.IsAbstract)
{
throw new InvalidOperationException(
e.UnregisteredServiceType.ToFriendlyName() + " has not been registered.");
}
}; |
Branch feature-377 created. |
I decided to add an
For v4.x, the default option will be An explanation of what the different options entail, can be seen in the definion of the enum: /// <summary>
/// This enumeration defines the container's behavior for resolving concrete unregistered
/// types.
/// </summary>
public enum ConcreteTypeResolutionOption
{
/// <summary>
/// Specifies that the container will never try to resolve an unregistered concrete type
/// and instead throw an exception when such concrete type is requested, either directly
/// or when injected as dependency of a consuming type.
/// </summary>
Never = 0,
/// <summary>
/// Specifies that the container will only try to resolve an unregistered concrete type in
/// case it is requested as dependency of a consuming type, but never when requested
/// directly (i.e. as root type) by calling GetInstance or similar methods.
/// </summary>
OnlyDependencies = 1,
/// <summary>
/// Specifies that the container will always try to resolve an unregistered concrete type
/// when it is requested.
/// </summary>
Always = 2,
} This feature is planned to be released in v4.5. Any feedback on this is welcome. |
I'm not sure if the option What are the use cases for this option? |
The idea of the My experience is that roughly half of Simple Injector's users depend on Simple Injector's ability to create unregistered types. Usage can be divided into two categories, namely: developers who resolve unregistered root types (like MVC controllers, etc), and developers who let components depend on concrete types that they, for the sake of simplicity, don't register. (and of course there will be developers that do both things, both knowingly and unknowingly). Because this many developers are depending on this exact behavior, changing this is a huge breaking change. It will impact many users and understand why this change was made and making the changes can potentially take a considerable amount of time. Unregistered root types are the biggest risk as they disallow Simple Injector's diagnostic system from doing a complete analysis on the complete graph (as the root types are unknown during verification). This problem does not exist with unregistered dependencies as Simple Injector will find them anyway when it analysis the consuming types. Simple Injector also contains several verification checks to check the sanity of these unregistered types. This makes unregistered dependencies much less risky. So the idea here is to spread this breaking change out over time:
|
@dotnetjunkie This last comment convinces me. Let's do this in the proposed time schedule. This sounds like a good path to follow which will trip the most lazy users at first (from v5). Thereby tremendously increasing the safety net, because now almost any type is in the container to be verified. I guess Can you share a case where |
I think, eventually, |
I agree that @TheBigRic one example: @dotnetjunkie maybe it's worth considering making 'Never' the default for v5 already. Not all users will check the roadmap and understand that |
@aKzenT, you bring up a fair point. The main reason for me to introduce the third Because of that, I reverted my decision to go with the three-state solution and the |
…isable creation of unregistered concrete types. Fixes #377.
TLDR;
This issue introduces an
Container.Options.ResolveUnregisteredConcreteTypes
configuration flag that allows disabling resolving unregistered concrete types:This feature was added to Simple Injector v4.5. In Simple Injector v4.x it defaults to
true
, which is the pre-v4.5 behavior, which means Simple Injector will try to resolve concrete types on your behalf, even in the absence of a registration for that concrete type.Simple Injector v5.0 will change the default to
false
. You are, however, advised to change the setting tofalse
directly while working with Simple Injector v4.x. This prevents surprises when migrating to v5.x.The rest of this issue describes the long discussion about the design and behavior of this feature.
Simple Injector currently resolves unregistered concrete types by default. Although this is convenient in a number of cases, it can also lead to errors. Because of this, Simple Injector has several Diagnostic warnings that shield the user from making errors when using concrete unregistered types, such as:
Some improvements in v4 will even improve the likelihood that configuration mistakes are spotted.
Still, however, it is possible for users to shoot themselves in the foot, for instance by accidentally resolving a concrete type (when doing dispatching for instance) instead of the interface. This would prevent Simple Injector from being able to apply decorators.
Perhaps we should disable the creation of unregistered concrete types by default, to prevent these errors and supply a configuration switch that allows the user to re-enable this behavior, for legacy scenarios:
Let's discuss.
The text was updated successfully, but these errors were encountered: