-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
Issue 36015 new BinderOption to throw on missing configuration #53852
Issue 36015 new BinderOption to throw on missing configuration #53852
Conversation
Note regarding the This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, to please make sure the API implementation in the src *.cs file is documented with triple slash comments, so the PR reviewers can sign off that change. |
Tagging subscribers to this area: @maryamariyan, @safern Issue Details
|
src/libraries/Microsoft.Extensions.Configuration.Binder/src/BinderOptions.cs
Outdated
Show resolved
Hide resolved
src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs
Outdated
Show resolved
Hide resolved
src/libraries/Microsoft.Extensions.Configuration.Binder/src/Resources/Strings.resx
Show resolved
Hide resolved
8b53c47
to
ae0ae28
Compare
…rget. Remove the BindingException as that'd need another API review
ae0ae28
to
89de0f3
Compare
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationBinderTests.cs
Outdated
Show resolved
Hide resolved
src/libraries/Microsoft.Extensions.Primitives/src/Resources/Strings.resx
Outdated
Show resolved
Hide resolved
@@ -209,7 +209,27 @@ private static void BindNonScalar(this IConfiguration configuration, object inst | |||
{ | |||
if (instance != null) | |||
{ | |||
foreach (PropertyInfo property in GetAllProperties(instance.GetType())) | |||
List<PropertyInfo> modelProperties = GetAllProperties(instance.GetType()).ToList(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need the ToList()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see it is to enumerate the list twice. Maybe we can void it and check this as we bind the properties, aggregate the missing names on a list if the flag to throw is enabled and then throw?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If performance is the driving force behind this suggestion, then enumerating just once would be the best thing to do. I'm guessing though, that binding configuration won't be on a hot path. Hence, in my opinion, it is more beneficial this way as:
- the list of properties has already been gathered (we're just enumerating twice, not building it twice)
- it is logically self-contained; a distinct step before any binding is attempted
- it fails fast: if we check as we bind we could end up with an object which has some properties that are set and some which aren't set
- if we check as we bind, then the enumeration of property names in config would happen on
BindProperty
, which would result in more enumerations than we currently have
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those are valid points. Sounds good. I think since this is opt-in, I don't mind an extra enumeration, also Binding usually just happens once in a lifetime of the application.
What do you think of removing Linq usages? We have been trying to do that on these libraries.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm happy to remove Linq usages if that fits in with the plan for libraries. May I ask why though? I've certainly seen, in my day job, overuse of Linq especially in tight loops, which cause a lot of overhead for the GC with temporary objects. But outside of tight loops or hot paths, I think the declarative nature of Linq makes the code easier to understand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just looked into doing this imperatively by creating an empty List
to hold the missing property names and found that we could end up with a sparse array, which I estimate would counter any gains achieved by switching from Linq. One the one hand, we create a zero-sized List
, in which case the size doubles for every addition potentially leading to a sparse array, or we could create the List
with the same size as the amount of children and assume everything is missing, in which case it's likely that not everything is missing and we'll again end up with a sparse array.
I don't think these scenarios are tragic though; whether it's the overhead of a Linq enumerator, or a few dozen empty slots in an array, this usually happens just once.
Bearing this in mind, I think it's no less efficient to use Linq, but If you'd like me to switch, then I'm happy to do so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't mind an extra enumeration, also Binding usually just happens once in a lifetime of the application.
I think if you use IOptionsSnapshot
for example then binding could happen once per request
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, Microsoft.Extensions.Configuration has already used Linq to build the collection that we further 'compose'
The reason reducing Linq was brought up, has to do with this issue #44598, for example https://github.com/dotnet/runtime/pull/44825/files
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found this post by Andrew Lock:
https://andrewlock.net/the-dangers-and-gotchas-of-using-scoped-services-when-configuring-options-in-asp-net-core/
It says:
Note: As the strongly-typed settings are re-built with every request, and the binding relies on reflection under the hood, you should bear performance in mind. There is currently an open issue on GitHub to investigate performance.
It links to aspnet/Configuration#604
Seeing as Reflection is the slowest part of the process, I think the extra enumeration is fairly insignificant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GetAllProperties is a private method that's typed as returning IEnumerable<PropertyInfo>
, but its implementation is actually constructing and returning a List<PropertyInfo>
. The right change here then is to just change that private method to return a List<PropertyInfo>
and remove the ToList
call from the call site.
.Select(mp => $"'{mp.Key}'") | ||
.ToList(); | ||
|
||
if (missingPropertyNames.Any()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are creating a List
for these missing properties, you could use Length
instead and avoid Linq.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've used the Count
property. After about 15 years of .NET, I too still cannot remember whether it's .Length
or .Count
:) Luckily, ReSharper knows!
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationBinderTests.cs
Outdated
Show resolved
Hide resolved
src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
runtime pipeline for this PR: https://dnceng.visualstudio.com/public/_build/results?buildId=1191246&view=results |
Thank you very much @SteveDunn! Feel free to keep picking issues and we'll be happy to help and merge your contributions. |
Fixes: #36015