-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Support immutable types with configuration binding #67258
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
Changes from all commits
b2c6255
1d8d70b
a51db1a
0bdd214
d71d14a
a90ae61
9bb4cbf
e59e8ef
94e5c2b
13f6c37
4d04a4b
a9e5eb7
98be745
d690b95
eaa0ccb
2fb15ee
b948152
4dcc89f
3f10fcc
7b95565
88985ca
41a7119
6d54f27
fa8c4b5
11c7808
f2db596
2d57891
b41c603
99b3329
4e5f912
2918063
d7754c2
59ed6d3
ede16aa
0c558bc
5029862
6aa72ad
dee80fd
2a59c97
888672c
24cd5a4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ | |
| using System.Diagnostics.CodeAnalysis; | ||
| using System.Linq; | ||
| using System.Reflection; | ||
| using Microsoft.Extensions.Internal; | ||
|
|
||
| namespace Microsoft.Extensions.Configuration | ||
| { | ||
|
|
@@ -328,9 +329,10 @@ private static object BindToCollection(Type type, IConfiguration config, BinderO | |
|
|
||
| [RequiresUnreferencedCode(TrimmingWarningMessage)] | ||
| private static void BindInstance( | ||
| [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] | ||
| Type type, | ||
| BindingPoint bindingPoint, IConfiguration config, BinderOptions options) | ||
| [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type type, | ||
| BindingPoint bindingPoint, | ||
| IConfiguration config, | ||
| BinderOptions options) | ||
| { | ||
| // if binding IConfigurationSection, break early | ||
| if (type == typeof(IConfigurationSection)) | ||
|
|
@@ -381,7 +383,7 @@ private static void BindInstance( | |
| return; // We are already done if binding to a new collection instance worked | ||
| } | ||
|
|
||
| bindingPoint.SetValue(CreateInstance(type)); | ||
| bindingPoint.SetValue(CreateInstance(type, config, options)); | ||
| } | ||
|
|
||
| // See if it's a Dictionary | ||
|
|
@@ -407,23 +409,63 @@ private static void BindInstance( | |
| } | ||
| } | ||
|
|
||
| [RequiresUnreferencedCode("In case type is a Nullable<T>, cannot statically analyze what the underlying type is so its members may be trimmed.")] | ||
| private static object CreateInstance([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type type) | ||
| [RequiresUnreferencedCode( | ||
| "In case type is a Nullable<T>, cannot statically analyze what the underlying type is so its members may be trimmed.")] | ||
| private static object CreateInstance( | ||
| [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | | ||
| DynamicallyAccessedMemberTypes.NonPublicConstructors)] | ||
| Type type, | ||
| IConfiguration config, | ||
| BinderOptions options) | ||
| { | ||
| Debug.Assert(!type.IsArray); | ||
|
|
||
| if (type.IsAbstract) | ||
| if (type.IsInterface || type.IsAbstract) | ||
| { | ||
| throw new InvalidOperationException(SR.Format(SR.Error_CannotActivateAbstractOrInterface, type)); | ||
| } | ||
|
|
||
| if (!type.IsValueType) | ||
| ConstructorInfo[] constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance); | ||
|
|
||
| bool hasParameterlessConstructor = | ||
| type.IsValueType || constructors.Any(ctor => ctor.GetParameters().Length == 0); | ||
|
|
||
| if (!type.IsValueType && constructors.Length == 0) | ||
| { | ||
| throw new InvalidOperationException(SR.Format(SR.Error_MissingPublicInstanceConstructor, type)); | ||
| } | ||
|
|
||
| if (constructors.Length > 1 && !hasParameterlessConstructor) | ||
| { | ||
| bool hasDefaultConstructor = type.GetConstructors(DeclaredOnlyLookup).Any(ctor => ctor.IsPublic && ctor.GetParameters().Length == 0); | ||
| if (!hasDefaultConstructor) | ||
| throw new InvalidOperationException(SR.Format(SR.Error_MultipleParameterizedConstructors, type)); | ||
| } | ||
|
|
||
| if (constructors.Length == 1 && !hasParameterlessConstructor) | ||
| { | ||
| ConstructorInfo constructor = constructors[0]; | ||
| ParameterInfo[] parameters = constructor.GetParameters(); | ||
|
|
||
| if (!CanBindToTheseConstructorParameters(parameters, out string nameOfInvalidParameter)) | ||
| { | ||
| throw new InvalidOperationException(SR.Format(SR.Error_CannotBindToConstructorParameter, type, nameOfInvalidParameter)); | ||
| } | ||
|
|
||
|
|
||
| List<PropertyInfo> properties = GetAllProperties(type); | ||
|
|
||
| if (!DoAllParametersHaveEquivalentProperties(parameters, properties, out string nameOfInvalidParameters)) | ||
| { | ||
| throw new InvalidOperationException(SR.Format(SR.Error_ConstructorParametersDoNotMatchProperties, type, nameOfInvalidParameters)); | ||
| } | ||
|
|
||
| object?[] parameterValues = new object?[parameters.Length]; | ||
|
|
||
| for (int index = 0; index < parameters.Length; index++) | ||
| { | ||
| throw new InvalidOperationException(SR.Format(SR.Error_MissingParameterlessConstructor, type)); | ||
| parameterValues[index] = BindParameter(parameters[index], type, config, options); | ||
| } | ||
|
|
||
| return constructor.Invoke(parameterValues); | ||
|
||
| } | ||
|
|
||
| object? instance; | ||
|
|
@@ -439,6 +481,46 @@ private static object CreateInstance([DynamicallyAccessedMembers(DynamicallyAcce | |
| return instance ?? throw new InvalidOperationException(SR.Format(SR.Error_FailedToActivate, type)); | ||
| } | ||
|
|
||
| private static bool DoAllParametersHaveEquivalentProperties(ParameterInfo[] parameters, | ||
| List<PropertyInfo> properties, out string missing) | ||
| { | ||
| HashSet<string> propertyNames = new(StringComparer.OrdinalIgnoreCase); | ||
| foreach (PropertyInfo prop in properties) | ||
| { | ||
| propertyNames.Add(prop.Name); | ||
| } | ||
|
|
||
| List<string> missingParameters = new(); | ||
|
|
||
| foreach (ParameterInfo parameter in parameters) | ||
| { | ||
| string name = parameter.Name!; | ||
| if (!propertyNames.Contains(name)) | ||
| { | ||
| missingParameters.Add(name); | ||
| } | ||
| } | ||
|
|
||
| missing = string.Join(",", missingParameters); | ||
|
|
||
| return missing.Length == 0; | ||
| } | ||
|
|
||
| private static bool CanBindToTheseConstructorParameters(ParameterInfo[] constructorParameters, out string nameOfInvalidParameter) | ||
| { | ||
| nameOfInvalidParameter = string.Empty; | ||
| foreach (ParameterInfo p in constructorParameters) | ||
| { | ||
| if (p.IsOut || p.IsIn || p.ParameterType.IsByRef) | ||
| { | ||
| nameOfInvalidParameter = p.Name!; // never null as we're not passed return value parameters: https://docs.microsoft.com/en-us/dotnet/api/system.reflection.parameterinfo.name?view=net-6.0#remarks | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| [RequiresUnreferencedCode("Cannot statically analyze what the element type is of the value objects in the dictionary so its members may be trimmed.")] | ||
| private static void BindDictionary( | ||
| object dictionary, | ||
|
|
@@ -687,6 +769,40 @@ private static List<PropertyInfo> GetAllProperties( | |
| return allProperties; | ||
| } | ||
|
|
||
| [RequiresUnreferencedCode(PropertyTrimmingWarningMessage)] | ||
| private static object? BindParameter(ParameterInfo parameter, Type type, IConfiguration config, | ||
| BinderOptions options) | ||
| { | ||
| string? parameterName = parameter.Name; | ||
|
|
||
| if (parameterName is null) | ||
| { | ||
| throw new InvalidOperationException(SR.Format(SR.Error_ParameterBeingBoundToIsUnnamed, type)); | ||
| } | ||
|
|
||
| var propertyBindingPoint = new BindingPoint(initialValue: config.GetSection(parameterName).Value, isReadOnly: false); | ||
SteveDunn marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| if (propertyBindingPoint.Value is null) | ||
| { | ||
| if (ParameterDefaultValue.TryGetDefaultValue(parameter, out object? defaultValue)) | ||
| { | ||
| propertyBindingPoint.SetValue(defaultValue); | ||
| } | ||
| else | ||
| { | ||
| throw new InvalidOperationException(SR.Format(SR.Error_ParameterHasNoMatchingConfig, type, parameterName)); | ||
| } | ||
| } | ||
|
|
||
| BindInstance( | ||
| parameter.ParameterType, | ||
| propertyBindingPoint, | ||
| config.GetSection(parameterName), | ||
| options); | ||
|
|
||
| return propertyBindingPoint.Value; | ||
| } | ||
|
|
||
| private static string GetPropertyName(MemberInfo property) | ||
| { | ||
| ThrowHelper.ThrowIfNull(property); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.