-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Allow ConfigBinder to bind arrays to Singular elements #57204
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
Conversation
When trying to bind a collection of type `T`, ConfigBinder will now try to bind to the current object instead of children objects when the provided configuration doesn't contain an array. This change allows elements that are not arrays to be bound to arrays. For example, a type string[] can now be bound to "key" along with "key:0".
|
Tagging subscribers to this area: @maryamariyan, @safern Issue DetailsWhen trying to bind a collection of type This change allows elements that are not arrays to be bound to arrays. For example, a type string[] can now be bound to "key" in case "key:0" doesn't exist.
|
|
|
||
| foreach (IConfigurationSection section in config.GetChildren()) | ||
| IEnumerable<IConfigurationSection> children; | ||
| if (config.GetChildren().Any(a => long.TryParse(a.Key, out _))) |
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.
Can we extract this into a helper method and use it in both places? Also it would be nice to write a comment or two about what this is doing, and why.
|
To provide more context this is to be able to bind <TvShows>
<Metadata>
<Name>Prison Break</Name>
</Metadata>
</TvShows>public class TvShows
{
public Metadata[] Metadata { get; set; }
}
public class Metadata
{
public string Name { get; set; }
}The reason why we can't fix this in the provider is because we don't have a way to know when the current node is expected to bind to an array, so we can't just append a cc: @davidfowl @maryamariyan for thoughts on taking this change now as it kind of changes the expected behavior, i.e if I now have a {
"elements": "element1"
}elements could be bound to a |
|
Also, @HaoK for his thoughts. |
|
This doesn't sound unreasonable, but I'd put it behind a option flag for sure, since this seems reasonable safe to enable by default |
src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs
Outdated
Show resolved
Hide resolved
|
|
||
| IEnumerable<IConfigurationSection> children; | ||
| // If configuration's children is an array, the configuration key will be a number | ||
| if (config.GetChildren().Any(a => long.TryParse(a.Key, out _))) |
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.
@HaoK let us know if you know of a better way for this condition
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 any of the children is an integer (key:999), this assumes the actual object is an array.
In real world, either all children are integers or all are not. But doing "Any" prevents usages in weird cases where someone used both things
having key:a, key:0 doesn't make sense but i am assuming that it is an array (as in the old behavior)
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.
So the old behavior always used the children correct? Why does this need to check if any children are a number? What doesn't work if this is just returning the children if there are any, otherwise the config.
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.
This used to always return children for arrays/collections. But as part of my change, I might have to return the parent object or children object (when it contains array as a children vs non-array).
If children are a number, that means the children are an array and we select that. Otherwise, we select the parent object as a single-element array.
When there are nested objects (keys - "a:b", "a:c"), there are children but they cannot be bound. You have to select the parent object ("a" itself) for the single-element array to be bound.
maryamariyan
left a comment
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.
LGTM aside from comments
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationBinderTests.cs
Show resolved
Hide resolved
src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs
Show resolved
Hide resolved
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationBinderTests.cs
Outdated
Show resolved
Hide resolved
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/ConfigurationBinderTests.cs
Outdated
Show resolved
Hide resolved
| [Fact] | ||
| public void CannotBindSingleElementToCollectionWhenSwitchSet() | ||
| { | ||
| AppContext.SetSwitch("Microsoft.Extensions.Configuration.BindSingleElementsToArray", false); |
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.
In a perfect world, this should use RemoteExecutor. Since this switch is process-wide, setting this switch can affect other tests running in the process.
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.
Did we really add a global switch for configuration behavior. This makes me sad 😢
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.
We discussed it at length on in API Review. The decision was that we didn't think people need/want the old behavior. Thus we didn't want to add a public API for it. But since this is a behavior change, we wanted a way to turn the behavior off, just in case someone needed it.
See #57325 (comment)
|
|
||
| private static bool ShouldBindSingleElementsToArray() | ||
| { | ||
| if (AppContext.TryGetSwitch(BindSingleElementsToArraySwitch, out bool bindSingleElementsToArray)) |
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.
Does this need to be cached? Reading from AppContext each time in a loop might be a perf issue.
|
Added When you commit this breaking change:
Tagging @dotnet/compat for awareness of the breaking change. |
|
I'll apply the remaining feedback on a follow up PR. |
* Allow ConfigBinder to bind arrays to Singular elements (#57204) * Apply feedback from PR #57204 (#57872) Co-authored-by: vidommet <[email protected]>
|
@maryamariyan Could you please take a look at the problem caused by the changes #58330 |
This reverts commit e4a455f.
This reverts commit 7f595ce.
|
We reverted this PR in #59716, so I'll remove the breaking change labels rather than opening issue in dotnet/docs |
When trying to bind a collection of type
T, ConfigBinder will now try to bind to the current object instead of children objects when the provided configuration doesn't contain an array.This change allows elements that are not arrays to be bound to arrays. For example, a type string[] can now be bound to "key" in case "key:0" doesn't exist.