-
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
[release/8.0] Improve binder gen binding logic (init, member binding, polymorphism) #92118
Conversation
Tagging subscribers to this area: @dotnet/area-extensions-configuration Issue DetailsBackport of #91717 to release/8.0. Customer ImpactFixes two reported bugs. We expect users to hit them:
This is inconsistent with reflection. using Microsoft.Extensions.Configuration;
Environment.SetEnvironmentVariable("Value", "42");
IConfiguration b = new ConfigurationBuilder().AddEnvironmentVariables().Build();
Base d = new Derived();
b.Bind(d); // Throws exception saying it can't init, whereas it isn't expected to.
Console.WriteLine(d.Value);
internal abstract class Base
{
public int Value { get; set; }
}
internal sealed class Derived : Base { }
Given this shape class MyClass
{
public MyAbstractClass Member { get; set; }
}
public abstract MyAbstractClass { get; } The generator would emit calls to a method called TestingExtensive unit and emitted source regression tests were added to verify the fix for the reported scenarios. We also added tests for additional edge case scenarios. RiskLow. Contained fix for off-by-default component.
|
What about the case where the property exposes an existing instance and we're binding to that? Could we still set the members on that type without creating the instance? |
It looks like this case wasn't supported already and remains unsupported. I'll file a new bug on it. |
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 scenario is important to fix - binding to abstract types. This PR makes progress towards that goal. Of course fixing scenarios that have bad-code gen is a top priority.
I wish this were smaller, but I understand that some of the refactoring here was necessary for future changes and has been reviewed and approved by multiple folks familiar with the codebase.
I'll approve this to move forward with the work in this component. @artl93 would like your eyes here too.
@@ -177,60 +177,7 @@ public class MyClass { } | |||
} | |||
|
|||
[Fact] | |||
public async Task BindCanParseMethodParam() |
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 this test was moved to Bind_CanParseTargetConfigType_FromMethodParam
as a baseline test instead.
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.
Yeah it was a bit brittle as the entry point code evolved.
Assert.Equal(12, d.Where(diag => diag.Id == Diagnostics.TypeNotSupported.Id).Count()); | ||
Assert.Equal(10, d.Where(diag => diag.Id == Diagnostics.PropertyNotSupported.Id).Count()); | ||
Assert.Equal(47, d.Where(diag => diag.Id == Diagnostics.TypeNotSupported.Id).Count()); | ||
Assert.Equal(44, d.Where(diag => diag.Id == Diagnostics.PropertyNotSupported.Id).Count()); |
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 wonder if this exactly number is really something to assert against if it will churn a lot and is influenced by things outside this test. No need to address that here, but consider for future.
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.
M2 Approved.
Backport of #91717 to release/8.0.
Fixes #90974, #91324
Customer Impact
Fixes two reported bugs. We expect users to hit them:
Given this shape
The generator would emit calls to a method called
InitializeMyAbstractClass
, but not actually provide an implementation. With this fix, the application would compile successfully and throw at runtime if it needed to configureStuff
.Additional considerations
Wrt to runtime behavior, root level
Bind
calls for objects without a known/supported ctor would now bind to the input instance as expected. However if binding to a property declared as an abstract type and there's a matching config value in the payload, then the generator would throw an exception saying it can't init the property -- whether the property instance isnull
or not.We need a follow up to reuse non-
null
instances rather than throw, and only throw if the property instance isnull
. This will make the generator impl consistent with reflection.Value of this backport
As mentioned we've fixed root level binding, but the issue remains for properties. Though we need a follow up to address the gap (#92137), securing the fix for root level binding would still be really valuable to users. It's reasonable to believe that a non-trivial amount of real-world config payloads contain just primitives (e.g. numbers, booleans, strings) etc.
We also fix the compilation issue mentioned above. In general we've prioritized fixing compilation issues -- they are super disruptive. It's also an immediate blocker for customers trying to use the generator, whereas if the app compiled they could make progress and perhaps provide feedback / raise further issues.
In summary, we should consider these fixes as valuable enough to take this backport in as-is (and not block on #92137).
Testing
Automated unit and emitted source regression tests were added to verify the fix for the reported scenarios.
Risk
Low. Contained fix for off-by-default component.