diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs index 03345f3c47770f..8001e8f9175c0f 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs @@ -115,14 +115,15 @@ private void EmitGetCoreMethod() case ParsableFromStringSpec stringParsableType: { EmitCastToIConfigurationSection(); + + EmitStartBlock($"if ({Identifier.TryGetConfigurationValue}({Identifier.configuration}, {Identifier.key}: null, out string? {Identifier.value}))"); EmitBindingLogic( stringParsableType, - Expression.sectionValue, + Identifier.value, Expression.sectionPath, writeOnSuccess: parsedValueExpr => _writer.WriteLine($"return {parsedValueExpr};"), - checkForNullSectionValue: stringParsableType.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue, - defaultValueSource: null, - useIncrementalStringValueIdentifier: false); + checkForNullSectionValue: stringParsableType.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue); + EmitEndBlock(); // End if-check for input type. } break; case ConfigurationSectionSpec: @@ -183,14 +184,7 @@ private void EmitGetValueCoreMethod() _writer.WriteLine($@"{Identifier.IConfigurationSection} {Identifier.section} = {GetSectionFromConfigurationExpression(Identifier.key, addQuotes: false)};"); _writer.WriteLine(); - _writer.WriteLine($$""" - if ({{Expression.sectionValue}} is not string {{Identifier.value}}) - { - return null; - } - """); - - _writer.WriteLine(); + EmitStartBlock($"if ({Identifier.TryGetConfigurationValue}({Identifier.section}, {Identifier.key}: null, out string? {Identifier.value}) && !string.IsNullOrEmpty({Identifier.value}))"); bool isFirstType = true; foreach (TypeSpec type in targetTypes) @@ -203,12 +197,11 @@ private void EmitGetValueCoreMethod() Identifier.value, Expression.sectionPath, writeOnSuccess: (parsedValueExpr) => _writer.WriteLine($"return {parsedValueExpr};"), - checkForNullSectionValue: false, - defaultValueSource: null, - useIncrementalStringValueIdentifier: false); + checkForNullSectionValue: false); EmitEndBlock(); } + EmitEndBlock(); _writer.WriteLine(); _writer.WriteLine("return null;"); @@ -420,7 +413,8 @@ void EmitBindImplForMember(MemberSpec member) if (member is ParameterSpec parameter && parameter.ErrorOnFailedBinding) { // Add exception logic for parameter ctors; must be present in configuration object. - EmitThrowBlock(condition: "else"); + // In case of Arrays, we emit extra block to handle empty arrays. The throw block will not be `else` case at that time. + EmitThrowBlock(condition: _typeIndex.GetEffectiveTypeSpec(member.TypeRef) is ArraySpec ? $"if ({member.Name} is null)" : "else"); } _writer.WriteLine(); @@ -438,6 +432,9 @@ void EmitThrowBlock(string condition) => private void EmitHelperMethods() { + // This is used all the time Get, Bind, and GetValue methods. + EmitTryGetConfigurationValueMethod(); + // Emitted if we are to bind objects with complex members, or if we're emitting BindCoreMain or GetCore methods. bool emitAsConfigWithChildren = ShouldEmitMethods(MethodsToGen_CoreBindingHelper.AsConfigWithChildren); @@ -488,6 +485,24 @@ private void EmitHelperMethods() } } + private void EmitTryGetConfigurationValueMethod() + { + EmitBlankLineIfRequired(); + _writer.WriteLine($$""" + /// Tries to get the configuration value for the specified key. + public static bool {{Identifier.TryGetConfigurationValue}}({{Identifier.IConfiguration}} {{Identifier.configuration}}, string {{Identifier.key}}, out string? {{Identifier.value}}) + { + if ({{Identifier.configuration}} is {{Identifier.ConfigurationSection}} {{Identifier.section}}) + { + return {{Identifier.section}}.TryGetValue({{Identifier.key}}, out {{Identifier.value}}); + } + + {{Identifier.value}} = {{Identifier.key}} != null ? {{Identifier.configuration}}[{{Identifier.key}}] : {{Identifier.configuration}} is {{Identifier.IConfigurationSection}} sec ? sec.Value : null; + return {{Identifier.value}} != null; + } + """); + } + private void EmitValidateConfigurationKeysMethod() { const string keysIdentifier = "keys"; @@ -572,7 +587,7 @@ private void EmitGetBinderOptionsHelper() private void EmitEnumParseMethod() { - string exceptionArg1 = string.Format(ExceptionMessages.FailedBinding, $"{{{Identifier.path}}}", $"{{typeof(T)}}"); + string exceptionArg1 = string.Format(ExceptionMessages.FailedBinding, $"{{{Identifier.value} ?? \"null\"}}", $"{{{Identifier.path}}}", $"{{typeof(T)}}"); string parseEnumCall = _emitGenericParseEnum ? "Enum.Parse(value, ignoreCase: true)" : "(T)Enum.Parse(typeof(T), value, ignoreCase: true)"; _writer.WriteLine($$""" @@ -642,7 +657,7 @@ private void EmitPrimitiveParseMethod(ParsableFromStringSpec type) } } - string exceptionArg1 = string.Format(ExceptionMessages.FailedBinding, $"{{{Identifier.path}}}", $"{{typeof({typeFQN})}}"); + string exceptionArg1 = string.Format(ExceptionMessages.FailedBinding, $"{{{Identifier.value} ?? \"null\"}}", $"{{{Identifier.path}}}", $"{{typeof({typeFQN})}}"); EmitStartBlock($"public static {typeFQN} {TypeIndex.GetParseMethodName(type)}(string {Identifier.value}, string? {Identifier.path})"); EmitEndBlock($$""" @@ -695,14 +710,14 @@ private void EmitBindingLogicForEnumerableWithAdd(TypeRef elementTypeRef, string { case ParsableFromStringSpec stringParsableType: { + EmitStartBlock($"if ({Identifier.TryGetConfigurationValue}({Identifier.section}, {Identifier.key}: null, out string? {Identifier.value}))"); EmitBindingLogic( stringParsableType, - Expression.sectionValue, + Identifier.value, Expression.sectionPath, (parsedValueExpr) => _writer.WriteLine($"{addExpr}({parsedValueExpr});"), - checkForNullSectionValue: true, - defaultValueSource: null, - useIncrementalStringValueIdentifier: false); + checkForNullSectionValue: true); + EmitEndBlock(); // End if-check for input type. } break; case ConfigurationSectionSpec: @@ -754,9 +769,7 @@ private void EmitBindCoreImplForDictionary(DictionarySpec type) Expression.sectionKey, Expression.sectionPath, Emit_BindAndAddLogic_ForElement, - checkForNullSectionValue: false, - defaultValueSource: null, - useIncrementalStringValueIdentifier: false); + checkForNullSectionValue: false); void Emit_BindAndAddLogic_ForElement(string parsedKeyExpr) { @@ -764,14 +777,14 @@ void Emit_BindAndAddLogic_ForElement(string parsedKeyExpr) { case ParsableFromStringSpec stringParsableElementType: { + EmitStartBlock($"if ({Identifier.TryGetConfigurationValue}({Identifier.section}, {Identifier.key}: null, out string? {Identifier.value}))"); EmitBindingLogic( stringParsableElementType, - Expression.sectionValue, + Identifier.value, Expression.sectionPath, writeOnSuccess: parsedValueExpr => _writer.WriteLine($"{instanceIdentifier}[{parsedKeyExpr}] = {parsedValueExpr};"), - checkForNullSectionValue: true, - defaultValueSource: null, - useIncrementalStringValueIdentifier: false); + checkForNullSectionValue: true); + EmitEndBlock(); // End if-check for input type. } break; case ConfigurationSectionSpec: @@ -877,14 +890,54 @@ private bool EmitBindImplForMember( if (canSet && canGet) { EmitBlankLineIfRequired(); + string valueIdentifier = GetIncrementalIdentifier(Identifier.value); + EmitStartBlock($@"if ({Identifier.TryGetConfigurationValue}({Identifier.configuration}, {Identifier.key}: ""{member.ConfigurationKeyName}"", out string? {valueIdentifier}))"); + + // Decide to emit the null check block for nullable types (e.g. int?). + // We don't emit this block for types that can be assigned directly from IConfigurationSection.Value as the valueIdentifier value can assigned + // anyway to the memberAccessExpr regardless of the nullability. This can reduce the emitted code size when assigning objects or strings which + // are common cases. + bool emitNullCheck = member.TypeRef.CanBeNull && stringParsableType.StringParsableTypeKind != StringParsableTypeKind.AssignFromSectionValue; + + // Nullable type can be set to null + if (emitNullCheck) + { + EmitStartBlock($"if ({valueIdentifier} is null)"); + _writer.WriteLine($"{memberAccessExpr} = null;"); + EmitEndBlock(); // End if-check for input type. + EmitStartBlock($"else"); + } + EmitBindingLogic( stringParsableType, - $@"{Identifier.configuration}[""{member.ConfigurationKeyName}""]", + valueIdentifier, sectionPathExpr, writeOnSuccess: parsedValueExpr => _writer.WriteLine($"{memberAccessExpr} = {parsedValueExpr};"), - checkForNullSectionValue: true, - defaultValueSource: initializationKind == InitializationKind.Declaration ? memberAccessExpr : null, - useIncrementalStringValueIdentifier: true); + checkForNullSectionValue: true); + + if (emitNullCheck) + { + EmitEndBlock(); // end of $"if ({valueIdentifier} is null)" + } + + EmitEndBlock(); // End if-check for input type. + + if (initializationKind == InitializationKind.Declaration) + { + EmitStartBlock($"else if (defaultValueIfNotFound)"); + if (!stringParsableType.TypeRef.CanBeNull) + { + _writer.WriteLine($"{memberAccessExpr} = {memberAccessExpr};"); + } + else + { + _writer.WriteLine($"var currentValue = {memberAccessExpr};"); + EmitStartBlock($"if (currentValue is not null)"); + _writer.WriteLine($"{memberAccessExpr} = currentValue;"); + EmitEndBlock(); + } + EmitEndBlock(); + } } return true; @@ -910,14 +963,27 @@ complexType is not CollectionSpec && return false; } - string sectionValidationCall = $"{MethodsToGen_CoreBindingHelper.AsConfigWithChildren}({sectionParseExpr})"; + EmitBlankLineIfRequired(); + string configSection = GetIncrementalIdentifier(Identifier.value); + _writer.WriteLine($"var {configSection} = {sectionParseExpr};"); + + string sectionValidationCall = $"{MethodsToGen_CoreBindingHelper.AsConfigWithChildren}({configSection})"; string sectionIdentifier = GetIncrementalIdentifier(Identifier.section); - EmitBlankLineIfRequired(); EmitStartBlock($"if ({sectionValidationCall} is {Identifier.IConfigurationSection} {sectionIdentifier})"); EmitBindingLogicForComplexMember(member, memberAccessExpr, sectionIdentifier, canSet); EmitEndBlock(); + // The current configuration section doesn't have any children, let's check if we are binding to an array and the configuration value is empty string. + // In this case, we will assign an empty array to the member. Otherwise, we will skip the binding logic. + if (complexType is ArraySpec arraySpec && canSet) + { + string valueIdentifier = GetIncrementalIdentifier(Identifier.value); + EmitStartBlock($@"if ({memberAccessExpr} is null && {Identifier.TryGetConfigurationValue}({configSection}, {Identifier.key}: null, out string? {valueIdentifier}) && {valueIdentifier} == string.Empty)"); + _writer.WriteLine($"{memberAccessExpr} = global::System.{Identifier.Array}.Empty<{arraySpec.ElementTypeRef.FullyQualifiedName}>();"); + EmitEndBlock(); + } + return _typeIndex.CanInstantiate(complexType); } default: @@ -1096,51 +1162,39 @@ private void EmitBindingLogic( string sectionValueExpr, string sectionPathExpr, Action? writeOnSuccess, - bool checkForNullSectionValue, - string? defaultValueSource, - bool useIncrementalStringValueIdentifier) + bool checkForNullSectionValue) { StringParsableTypeKind typeKind = type.StringParsableTypeKind; Debug.Assert(typeKind is not StringParsableTypeKind.None); - string nonNull_StringValue_Identifier = useIncrementalStringValueIdentifier ? GetIncrementalIdentifier(Identifier.value) : Identifier.value; - string stringValueToParse_Expr = checkForNullSectionValue ? nonNull_StringValue_Identifier : sectionValueExpr; string parsedValueExpr = typeKind switch { - StringParsableTypeKind.AssignFromSectionValue => stringValueToParse_Expr, - StringParsableTypeKind.Enum => $"ParseEnum<{type.TypeRef.FullyQualifiedName}>({stringValueToParse_Expr}, {sectionPathExpr})", - _ => $"{TypeIndex.GetParseMethodName(type)}({stringValueToParse_Expr}, {sectionPathExpr})", + StringParsableTypeKind.AssignFromSectionValue => sectionValueExpr, + StringParsableTypeKind.Enum => $"ParseEnum<{type.TypeRef.FullyQualifiedName}>({sectionValueExpr}, {sectionPathExpr})", + _ => $"{TypeIndex.GetParseMethodName(type)}({sectionValueExpr}, {sectionPathExpr})", }; - if (!checkForNullSectionValue) + // Usually assigning the configuration value to string or object + if (!checkForNullSectionValue || typeKind == StringParsableTypeKind.AssignFromSectionValue) { writeOnSuccess?.Invoke(parsedValueExpr); } else { - // In case of calling parsing methods, check the section value first for null or empty before calling parse. - string extraCondition = typeKind == StringParsableTypeKind.AssignFromSectionValue ? "" : $" && !string.IsNullOrEmpty({nonNull_StringValue_Identifier})"; - EmitStartBlock($"if ({sectionValueExpr} is string {nonNull_StringValue_Identifier}{extraCondition})"); - writeOnSuccess?.Invoke(parsedValueExpr); - EmitEndBlock(); - } - - if (defaultValueSource is not null) - { - Debug.Assert(checkForNullSectionValue); + // call parsing methods - EmitStartBlock($"else if (defaultValueIfNotFound)"); - if (!type.TypeRef.CanBeNull) - { - writeOnSuccess?.Invoke(defaultValueSource); - } - else + string conditionPrefix = string.Empty; + // Special case ByteArray when having empty string configuration value as we need to assign empty byte array at that time. + if (typeKind == StringParsableTypeKind.ByteArray) { - _writer.WriteLine($"var currentValue = {defaultValueSource};"); - EmitStartBlock($"if (currentValue is not null)"); - writeOnSuccess?.Invoke("currentValue"); + EmitStartBlock($"if ({sectionValueExpr} == string.Empty)"); + writeOnSuccess?.Invoke(parsedValueExpr); EmitEndBlock(); + conditionPrefix = "else "; } + + EmitStartBlock($"{conditionPrefix}if (!string.IsNullOrEmpty({sectionValueExpr}))"); + writeOnSuccess?.Invoke(parsedValueExpr); EmitEndBlock(); } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/ExceptionMessages.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/ExceptionMessages.cs index 696af27d697374..beb0b564bb7f9d 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/ExceptionMessages.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/ExceptionMessages.cs @@ -13,7 +13,7 @@ internal static class ExceptionMessages public const string CannotBindToConstructorParameter = "Cannot create instance of type '{0}' because one or more parameters cannot be bound to. Constructor parameters cannot be declared as in, out, or ref. Invalid parameters are: '{1}'"; public const string CannotSpecifyBindNonPublicProperties = "The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."; public const string ConstructorParametersDoNotMatchProperties = "Cannot create instance of type '{0}' because one or more parameters cannot be bound to. Constructor parameters must have corresponding properties. Fields are not supported. Missing properties are: '{1}'"; - public const string FailedBinding = "Failed to convert configuration value at '{0}' to type '{1}'."; + public const string FailedBinding = "Failed to convert configuration value '{0}' at '{1}' to type '{2}'."; public const string MissingConfig = "'{0}' was set on the provided {1}, but the following properties were not found on the instance of {2}: {3}"; public const string MissingPublicInstanceConstructor = "Cannot create instance of type '{0}' because it is missing a public instance constructor."; public const string MultipleParameterizedConstructors = "Cannot create instance of type '{0}' because it has multiple public parameterized constructors."; diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/Helpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/Helpers.cs index 9d340ac0e93c55..cf9511cf5a6f90 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/Helpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/Helpers.cs @@ -114,6 +114,7 @@ private static class Identifier public const string HasValue = nameof(HasValue); public const string IConfiguration = nameof(IConfiguration); public const string IConfigurationSection = nameof(IConfigurationSection); + public const string ConfigurationSection = nameof(ConfigurationSection); public const string Int32 = "int"; public const string InterceptsLocation = nameof(InterceptsLocation); public const string InvalidOperationException = nameof(InvalidOperationException); @@ -133,6 +134,7 @@ private static class Identifier public const string Type = nameof(Type); public const string Uri = nameof(Uri); public const string ValidateConfigurationKeys = nameof(ValidateConfigurationKeys); + public const string TryGetConfigurationValue = nameof(TryGetConfigurationValue); public const string Value = nameof(Value); } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs index f5c6c05ba9616e..a34553e73cd3d2 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs @@ -311,7 +311,8 @@ private static void BindProperty(PropertyInfo property, object instance, IConfig // For property binding, there are some cases when HasNewValue is not set in BindingPoint while a non-null Value inside that object can be retrieved from the property getter. // As example, when binding a property which not having a configuration entry matching this property and the getter can initialize the Value. // It is important to call the property setter as the setters can have a logic adjusting the Value. - if (!propertyBindingPoint.IsReadOnly && propertyBindingPoint.Value is not null) + // Otherwise, if the HasNewValue set to true, it means that the property setter should be called anyway as encountering a new value. + if (!propertyBindingPoint.IsReadOnly && (propertyBindingPoint.Value is not null || propertyBindingPoint.HasNewValue)) { property.SetValue(instance, propertyBindingPoint.Value); } @@ -338,15 +339,41 @@ private static void BindInstance( return; } - var section = config as IConfigurationSection; - string? configValue = section?.Value; - if (configValue != null && TryConvertValue(type, configValue, section?.Path, out object? convertedValue, out Exception? error)) + IConfigurationSection? section; + string? configValue; + bool isConfigurationExist; + + if (config is ConfigurationSection configSection) + { + section = configSection; + isConfigurationExist = configSection.TryGetValue(key:null, out configValue); + } + else + { + section = config as IConfigurationSection; + configValue = section?.Value; + isConfigurationExist = configValue != null; + } + + if (isConfigurationExist && TryConvertValue(type, configValue, section?.Path, out object? convertedValue, out Exception? error)) { if (error != null) { throw error; } + if (type == typeof(byte[]) && bindingPoint.Value is byte[] byteArray && byteArray.Length > 0) + { + if (convertedValue is byte[] convertedByteArray && convertedByteArray.Length > 0) + { + Array a = Array.CreateInstance(type.GetElementType()!, byteArray.Length + convertedByteArray.Length); + Array.Copy(byteArray, a, byteArray.Length); + Array.Copy(convertedByteArray, 0, a, byteArray.Length, convertedByteArray.Length); + bindingPoint.TrySetValue(a); + } + return; + } + // Leaf nodes are always reinitialized bindingPoint.TrySetValue(convertedValue); return; @@ -476,13 +503,29 @@ private static void BindInstance( if (options.ErrorOnUnknownConfiguration) { Debug.Assert(section is not null); - throw new InvalidOperationException(SR.Format(SR.Error_FailedBinding, section.Path, type)); + throw new InvalidOperationException(SR.Format(SR.Error_FailedBinding, configValue, section.Path, type)); } } - else if (isParentCollection && bindingPoint.Value is null) + else { - // Try to create the default instance of the type - bindingPoint.TrySetValue(CreateInstance(type, config, options, out _)); + if (isParentCollection && bindingPoint.Value is null) + { + // Try to create the default instance of the type + bindingPoint.TrySetValue(CreateInstance(type, config, options, out _)); + } + else if (isConfigurationExist && bindingPoint.Value is null) + { + // Don't override the existing array in bindingPoint.Value if it is already set. + if (type.IsArray || IsImmutableArrayCompatibleInterface(type)) + { + // When having configuration value set to empty string, we create an empty array + bindingPoint.TrySetValue(configValue is null ? null : Array.CreateInstance(type.GetElementType()!, 0)); + } + else + { + bindingPoint.TrySetValue(bindingPoint.Value); // force setting null value + } + } } } } @@ -930,7 +973,7 @@ private static Array BindArray(Type type, IEnumerable? source, IConfiguration co private static bool TryConvertValue( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type type, - string value, string? path, out object? result, out Exception? error) + string? value, string? path, out object? result, out Exception? error) { error = null; result = null; @@ -954,11 +997,14 @@ private static bool TryConvertValue( { try { - result = converter.ConvertFromInvariantString(value); + if (value is not null) + { + result = converter.ConvertFromInvariantString(value); + } } catch (Exception ex) { - error = new InvalidOperationException(SR.Format(SR.Error_FailedBinding, path, type), ex); + error = new InvalidOperationException(SR.Format(SR.Error_FailedBinding, value, path, type), ex); } return true; } @@ -967,11 +1013,14 @@ private static bool TryConvertValue( { try { - result = Convert.FromBase64String(value); + if (value is not null ) + { + result = value == string.Empty ? Array.Empty() : Convert.FromBase64String(value); + } } catch (FormatException ex) { - error = new InvalidOperationException(SR.Format(SR.Error_FailedBinding, path, type), ex); + error = new InvalidOperationException(SR.Format(SR.Error_FailedBinding, value, path, type), ex); } return true; } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/Microsoft.Extensions.Configuration.Binder.csproj b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/Microsoft.Extensions.Configuration.Binder.csproj index 4fdca78c2bcdde..1b594196a3a8ee 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/Microsoft.Extensions.Configuration.Binder.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/Microsoft.Extensions.Configuration.Binder.csproj @@ -33,6 +33,7 @@ + diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/Resources/Strings.resx b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/Resources/Strings.resx index a926fc42386a9b..4bf9b6d5b84a9a 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/Resources/Strings.resx +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/Resources/Strings.resx @@ -1,17 +1,17 @@  - @@ -127,7 +127,7 @@ Cannot create instance of type '{0}' because one or more parameters cannot be bound to. Constructor parameters must have corresponding properties. Fields are not supported. Missing properties are: '{1}' - Failed to convert configuration value at '{0}' to type '{1}'. + Failed to convert configuration value '{0}' at '{1}' to type '{2}'. Failed to create instance of type '{0}'. @@ -153,4 +153,4 @@ Cannot create instance of type '{0}' because multidimensional arrays are not supported. - \ No newline at end of file + diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.Collections.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.Collections.cs index 14b164b3f0acda..6ec4a5024eda98 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.Collections.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.Collections.cs @@ -61,7 +61,7 @@ public void GetListNullValues() var list = new List(); config.GetSection("StringList").Bind(list); - Assert.Empty(list); + Assert.Equal([ null, null, null, null ], list); } [ConditionalFact(typeof(TestHelpers), nameof(TestHelpers.NotSourceGenMode))] // Ensure exception messages are in sync @@ -2182,9 +2182,10 @@ public void CanBindInstantiatedIEnumerableWithNullItems() var options = config.Get()!; - Assert.Equal(2, options.InstantiatedIEnumerable.Count()); - Assert.Equal("Yo1", options.InstantiatedIEnumerable.ElementAt(0)); - Assert.Equal("Yo2", options.InstantiatedIEnumerable.ElementAt(1)); + Assert.Equal(3, options.InstantiatedIEnumerable.Count()); + Assert.Null(options.InstantiatedIEnumerable.ElementAt(0)); + Assert.Equal("Yo1", options.InstantiatedIEnumerable.ElementAt(1)); + Assert.Equal("Yo2", options.InstantiatedIEnumerable.ElementAt(2)); } [Fact] diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.TestClasses.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.TestClasses.cs index 66d91cc0655304..a36c3b2d6e9e24 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.TestClasses.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.TestClasses.cs @@ -1147,5 +1147,36 @@ public enum MyValue Value2, Value3 } + + public class NullConfiguration + { + public NullConfiguration() + { + // Initialize with non-default value to ensure binding will override these values + StringProperty1 = "Initial Value 1"; + StringProperty2 = "Initial Value 2"; + StringProperty3 = "Initial Value 3"; + + IntProperty1 = 123; + IntProperty2 = 456; + } + public string? StringProperty1 { get; set; } + public string? StringProperty2 { get; set; } + public string? StringProperty3 { get; set; } + + public int? IntProperty1 { get; set; } + public int? IntProperty2 { get; set; } + } + + public class ArraysContainer + { + public string[] StringArray1 { get; set; } + public string[] StringArray2 { get; set; } + public string[] StringArray3 { get; set; } + + public byte[] ByteArray1 { get; set; } + public byte[] ByteArray2 { get; set; } + public byte[] ByteArray3 { get; set; } + } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs index f949ea10dc87e5..a08ff66bd1e4d1 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs @@ -6,8 +6,10 @@ using System.ComponentModel; using System.Diagnostics; using System.Globalization; +using System.IO; using System.Linq; using System.Reflection; +using System.Text; #if BUILDING_SOURCE_GENERATOR_TESTS using Microsoft.Extensions.Configuration; #endif @@ -208,14 +210,8 @@ public void EmptyStringIsNullable() configurationBuilder.AddInMemoryCollection(dic); var config = configurationBuilder.Build(); -#if BUILDING_SOURCE_GENERATOR_TESTS - // Ensure exception messages are in sync - Assert.Throws(() => config.GetValue("empty")); - Assert.Throws(() => config.GetValue("empty")); -#else Assert.Null(config.GetValue("empty")); Assert.Null(config.GetValue("empty")); -#endif } [Fact] @@ -326,8 +322,8 @@ public void CanBindToObjectProperty() [Fact] public void GetNullValue() { - #nullable enable - #pragma warning disable IDE0004 // Cast is redundant +#nullable enable +#pragma warning disable IDE0004 // Cast is redundant var dic = new Dictionary { @@ -377,8 +373,8 @@ public void GetNullValue() Assert.Equal(0, config.GetSection("Nested:Integer").Get()); Assert.Null(config.GetSection("Object").Get()); - #pragma warning restore IDE0004 - #nullable restore +#pragma warning restore IDE0004 +#nullable restore } [Fact] @@ -619,13 +615,13 @@ public void ConsistentExceptionOnFailedBinding(Type type) Assert.NotNull(exception.InnerException); Assert.NotNull(getException.InnerException); Assert.Equal( - SR.Format(SR.Error_FailedBinding, ConfigKey, type), + SR.Format(SR.Error_FailedBinding, IncorrectValue, ConfigKey, type), exception.Message); Assert.Equal( - SR.Format(SR.Error_FailedBinding, ConfigKey, type), + SR.Format(SR.Error_FailedBinding, IncorrectValue, ConfigKey, type), getException.Message); Assert.Equal( - SR.Format(SR.Error_FailedBinding, ConfigKey, type), + SR.Format(SR.Error_FailedBinding, IncorrectValue, ConfigKey, type), getValueException.Message); } @@ -649,7 +645,7 @@ public void ExceptionOnFailedBindingIncludesPath() var exception = Assert.Throws( () => config.Bind(options)); - Assert.Equal(SR.Format(SR.Error_FailedBinding, ConfigKey, typeof(int)), + Assert.Equal(SR.Format(SR.Error_FailedBinding, IncorrectValue, ConfigKey, typeof(int)), exception.Message); } @@ -1793,10 +1789,9 @@ public void ExceptionWhenTryingToBindToByteArray() configurationBuilder.AddInMemoryCollection(dic); var config = configurationBuilder.Build(); - var exception = Assert.Throws( - () => config.Get()); + var exception = Assert.Throws(() => config.Get()); Assert.Equal( - SR.Format(SR.Error_FailedBinding, "MyByteArray", typeof(byte[])), + SR.Format(SR.Error_FailedBinding, "(not a valid base64 string)", "MyByteArray", typeof(byte[])), exception.Message); } @@ -2813,7 +2808,7 @@ public void CanGetEnumerableNotCollection() Assert.Equal("John,Jane,Stephen", result.Names); Assert.True(result.Enabled); - Assert.Equal(new [] { "new", "class", "rosebud"}, result.Keywords); + Assert.Equal(new[] { "new", "class", "rosebud" }, result.Keywords); } [Fact] @@ -2855,5 +2850,162 @@ public void EnsureThrowingWithCollectionAndErrorOnUnknownConfigurationOption() internal class TestSettings { public Dictionary Values { get; init; } = []; } #endif + + [Fact] + public void BindWithNullValues() + { + // + // Try json provider first which used to replace the null configuration values with empty strings. + // Now it should be able to bind null values correctly and not replacing them. + // + + string jsonConfig = @" + { + ""NullConfiguration"": { + ""StringProperty1"": ""New Value!"", + ""StringProperty2"": null, + ""StringProperty3"": """", + ""IntProperty1"": 42, + ""IntProperty2"": null, + }, + }"; + + var configuration = new ConfigurationBuilder() + .AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(jsonConfig))) + .Build().GetSection("NullConfiguration"); + + NullConfiguration result = configuration.Get(); + + Assert.NotNull(result); + Assert.Equal("New Value!", result.StringProperty1); + Assert.Null(result.StringProperty2); + Assert.Null(result.IntProperty2); + Assert.Equal("", result.StringProperty3); + Assert.Equal(42, result.IntProperty1); + + // + // Test with in-memory configuration provider which never replaced the null values with empty strings. + // But the binder used to treat the null values as non-existing values and not bind them at all. + // + + var inMemoryConfiguration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + { "NullConfiguration:StringProperty1", "New Value!" }, + { "NullConfiguration:StringProperty2", null }, + { "NullConfiguration:StringProperty3", "" }, + { "NullConfiguration:IntProperty1", "42" }, + { "NullConfiguration:IntProperty2", null } + }) + .Build().GetSection("NullConfiguration"); + + NullConfiguration inMemoryResult = inMemoryConfiguration.Get(); + + Assert.NotNull(inMemoryResult); + + Assert.Equal("New Value!", inMemoryResult.StringProperty1); + + Assert.Null(inMemoryResult.StringProperty2); + Assert.Null(inMemoryResult.IntProperty2); + Assert.Equal("", inMemoryResult.StringProperty3); + Assert.Equal(42, inMemoryResult.IntProperty1); + } + + [Fact] + public void BindArraysWithNullAndOtherValues() + { + // Arrays like other collection when binding, it will merge the existing values with the new ones we get from the configuration. + // Ensure null, empty, and other values work as expected. + + string jsonConfig = @" + { + ""ArraysContainer"": { + ""StringArray1"": [""Value1"", ""Value2""], + ""StringArray2"": null, + ""StringArray3"": """", // should result empty array + + // We can bind byte array values from base64 strings too. Let's cover this case too. + ""ByteArray1"": null, + ""ByteArray2"": """", + ""ByteArray3"": ""AAECAwQFBgcICQo="" // encode byte values [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + }, + }"; + + var configuration = new ConfigurationBuilder() + .AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(jsonConfig))) + .Build().GetSection("ArraysContainer"); + + ArraysContainer instance = new(); // all properties are initialized to null. + configuration.Bind(instance); + + Assert.NotNull(instance); + Assert.Equal(["Value1", "Value2"], instance.StringArray1); + Assert.Null(instance.StringArray2); + + Assert.Empty(instance.StringArray3); // empty string should result in empty array + Assert.Empty(instance.ByteArray2); // empty string should result in empty array + + Assert.Null(instance.ByteArray1); + Assert.Equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], instance.ByteArray3); + + // Bind one more time and ensure the values are accumulated correctly. + configuration.Bind(instance); + Assert.Equal(["Value1", "Value2", "Value1", "Value2"], instance.StringArray1); + Assert.Null(instance.StringArray2); + Assert.Empty(instance.StringArray3); // empty string should result in empty array + Assert.Empty(instance.ByteArray2); // empty string should result in empty array + + Assert.Null(instance.ByteArray1); +#if BUILDING_SOURCE_GENERATOR_TESTS + // Source gen has different behavior with the byte array which should be addressed later + // Source gen override the existing array instead of merging the values. + Assert.Equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], instance.ByteArray3); +#else + Assert.Equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], instance.ByteArray3); +#endif + + // Test the same accumulation behavior with in-memory configuration + var inMemoryConfiguration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + // String arrays - use indexed keys for array elements + { "ArraysContainer:StringArray1:0", "Value1" }, + { "ArraysContainer:StringArray1:1", "Value2" }, + { "ArraysContainer:StringArray2", null }, + { "ArraysContainer:StringArray3", "" }, + + // Byte arrays + { "ArraysContainer:ByteArray1", null }, + { "ArraysContainer:ByteArray2", "" }, + { "ArraysContainer:ByteArray3", "AAECAwQFBgcICQo=" } // encode byte values [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + }) + .Build().GetSection("ArraysContainer"); + + ArraysContainer inMemoryInstance = new(); + inMemoryConfiguration.Bind(inMemoryInstance); + Assert.Equal(["Value1", "Value2"], inMemoryInstance.StringArray1); + Assert.Null(inMemoryInstance.StringArray2); + Assert.Empty(inMemoryInstance.StringArray3); // empty string should result in empty array + Assert.Empty(inMemoryInstance.ByteArray2); // empty string should result in empty array + + Assert.Null(inMemoryInstance.ByteArray1); + Assert.Equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], inMemoryInstance.ByteArray3); + + // Bind one more time and ensure the values are accumulated correctly. + inMemoryConfiguration.Bind(inMemoryInstance); + Assert.Equal(["Value1", "Value2", "Value1", "Value2"], inMemoryInstance.StringArray1); + Assert.Null(inMemoryInstance.StringArray2); + Assert.Empty(inMemoryInstance.StringArray3); // empty string should result in empty array + Assert.Empty(inMemoryInstance.ByteArray2); // empty string should result in empty array + + Assert.Null(inMemoryInstance.ByteArray1); +#if BUILDING_SOURCE_GENERATOR_TESTS + // Source gen has different behavior with the byte array which should be addressed later + // Source gen override the existing array instead of merging the values. + Assert.Equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], instance.ByteArray3); +#else + Assert.Equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], instance.ByteArray3); +#endif + } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind.generated.txt index d969a2ce0a9d3d..3e57ba50f61842 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind.generated.txt @@ -96,9 +96,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -107,7 +110,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { instance[section.Key] = value; } @@ -130,7 +133,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value0) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value0)) { instance.MyString = value0; } @@ -143,45 +146,51 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value1 && !string.IsNullOrEmpty(value1)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value1)) { - instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value1)) + { + instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + var value2 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value2) is IConfigurationSection section3) { - global::System.Collections.Generic.List? temp4 = instance.MyList; - temp4 ??= new global::System.Collections.Generic.List(); - BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp4; + global::System.Collections.Generic.List? temp5 = instance.MyList; + temp5 ??= new global::System.Collections.Generic.List(); + BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp5; } else { instance.MyList = instance.MyList; } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + var value6 = configuration.GetSection("MyDictionary"); + if (AsConfigWithChildren(value6) is IConfigurationSection section7) { - global::System.Collections.Generic.Dictionary? temp7 = instance.MyDictionary; - temp7 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp7; + global::System.Collections.Generic.Dictionary? temp9 = instance.MyDictionary; + temp9 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp9; } else { instance.MyDictionary = instance.MyDictionary; } - if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) + var value10 = configuration.GetSection("MyComplexDictionary"); + if (AsConfigWithChildren(value10) is IConfigurationSection section11) { - global::System.Collections.Generic.Dictionary? temp10 = instance.MyComplexDictionary; - temp10 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyComplexDictionary = temp10; + global::System.Collections.Generic.Dictionary? temp13 = instance.MyComplexDictionary; + temp13 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); + instance.MyComplexDictionary = temp13; } else { @@ -189,6 +198,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -247,7 +268,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Instance.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Instance.generated.txt index 243c0c75244179..ade1275493a46e 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Instance.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Instance.generated.txt @@ -60,9 +60,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -71,7 +74,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { instance[section.Key] = value; } @@ -94,7 +97,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value0) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value0)) { instance.MyString = value0; } @@ -107,45 +110,51 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value1 && !string.IsNullOrEmpty(value1)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value1)) { - instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value1)) + { + instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + var value2 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value2) is IConfigurationSection section3) { - global::System.Collections.Generic.List? temp4 = instance.MyList; - temp4 ??= new global::System.Collections.Generic.List(); - BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp4; + global::System.Collections.Generic.List? temp5 = instance.MyList; + temp5 ??= new global::System.Collections.Generic.List(); + BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp5; } else { instance.MyList = instance.MyList; } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + var value6 = configuration.GetSection("MyDictionary"); + if (AsConfigWithChildren(value6) is IConfigurationSection section7) { - global::System.Collections.Generic.Dictionary? temp7 = instance.MyDictionary; - temp7 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp7; + global::System.Collections.Generic.Dictionary? temp9 = instance.MyDictionary; + temp9 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp9; } else { instance.MyDictionary = instance.MyDictionary; } - if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) + var value10 = configuration.GetSection("MyComplexDictionary"); + if (AsConfigWithChildren(value10) is IConfigurationSection section11) { - global::System.Collections.Generic.Dictionary? temp10 = instance.MyComplexDictionary; - temp10 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyComplexDictionary = temp10; + global::System.Collections.Generic.Dictionary? temp13 = instance.MyComplexDictionary; + temp13 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); + instance.MyComplexDictionary = temp13; } else { @@ -153,6 +162,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -193,7 +214,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Instance_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Instance_BinderOptions.generated.txt index 52b29ac28967bb..71e72064b0b7b2 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Instance_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Instance_BinderOptions.generated.txt @@ -60,9 +60,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -71,7 +74,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { instance[section.Key] = value; } @@ -94,7 +97,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value0) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value0)) { instance.MyString = value0; } @@ -107,45 +110,51 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value1 && !string.IsNullOrEmpty(value1)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value1)) { - instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value1)) + { + instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + var value2 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value2) is IConfigurationSection section3) { - global::System.Collections.Generic.List? temp4 = instance.MyList; - temp4 ??= new global::System.Collections.Generic.List(); - BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp4; + global::System.Collections.Generic.List? temp5 = instance.MyList; + temp5 ??= new global::System.Collections.Generic.List(); + BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp5; } else { instance.MyList = instance.MyList; } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + var value6 = configuration.GetSection("MyDictionary"); + if (AsConfigWithChildren(value6) is IConfigurationSection section7) { - global::System.Collections.Generic.Dictionary? temp7 = instance.MyDictionary; - temp7 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp7; + global::System.Collections.Generic.Dictionary? temp9 = instance.MyDictionary; + temp9 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp9; } else { instance.MyDictionary = instance.MyDictionary; } - if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) + var value10 = configuration.GetSection("MyComplexDictionary"); + if (AsConfigWithChildren(value10) is IConfigurationSection section11) { - global::System.Collections.Generic.Dictionary? temp10 = instance.MyComplexDictionary; - temp10 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyComplexDictionary = temp10; + global::System.Collections.Generic.Dictionary? temp13 = instance.MyComplexDictionary; + temp13 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); + instance.MyComplexDictionary = temp13; } else { @@ -153,6 +162,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -211,7 +232,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Key_Instance.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Key_Instance.generated.txt index aa3c6a9e943107..2c0ef892a55e8d 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Key_Instance.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Key_Instance.generated.txt @@ -60,9 +60,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -71,7 +74,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { instance[section.Key] = value; } @@ -94,7 +97,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value0) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value0)) { instance.MyString = value0; } @@ -107,45 +110,51 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value1 && !string.IsNullOrEmpty(value1)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value1)) { - instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value1)) + { + instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + var value2 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value2) is IConfigurationSection section3) { - global::System.Collections.Generic.List? temp4 = instance.MyList; - temp4 ??= new global::System.Collections.Generic.List(); - BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp4; + global::System.Collections.Generic.List? temp5 = instance.MyList; + temp5 ??= new global::System.Collections.Generic.List(); + BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp5; } else { instance.MyList = instance.MyList; } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + var value6 = configuration.GetSection("MyDictionary"); + if (AsConfigWithChildren(value6) is IConfigurationSection section7) { - global::System.Collections.Generic.Dictionary? temp7 = instance.MyDictionary; - temp7 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp7; + global::System.Collections.Generic.Dictionary? temp9 = instance.MyDictionary; + temp9 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp9; } else { instance.MyDictionary = instance.MyDictionary; } - if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) + var value10 = configuration.GetSection("MyComplexDictionary"); + if (AsConfigWithChildren(value10) is IConfigurationSection section11) { - global::System.Collections.Generic.Dictionary? temp10 = instance.MyComplexDictionary; - temp10 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyComplexDictionary = temp10; + global::System.Collections.Generic.Dictionary? temp13 = instance.MyComplexDictionary; + temp13 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); + instance.MyComplexDictionary = temp13; } else { @@ -153,6 +162,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -193,7 +214,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_ParseTypeFromMethodParam.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_ParseTypeFromMethodParam.generated.txt index 9f8f951bff2980..190b61098f5fd2 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_ParseTypeFromMethodParam.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_ParseTypeFromMethodParam.generated.txt @@ -54,6 +54,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #endregion IConfiguration extensions. #region Core binding extensions. + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + public static BinderOptions? GetBinderOptions(Action? configureOptions) { if (configureOptions is null) diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get.generated.txt index 68ddfa4c042c13..6ea3124bfb01ee 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get.generated.txt @@ -89,9 +89,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -102,9 +105,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - temp2.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + temp2.Add(ParseInt(value, section.Path)); + } } } @@ -117,7 +123,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { instance[section.Key] = value; } @@ -128,7 +134,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value3) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value3)) { instance.MyString = value3; } @@ -141,45 +147,55 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value4 && !string.IsNullOrEmpty(value4)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value4)) { - instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value4)) + { + instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + var value5 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value5) is IConfigurationSection section6) { - global::System.Collections.Generic.List? temp7 = instance.MyList; - temp7 ??= new global::System.Collections.Generic.List(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp7; + global::System.Collections.Generic.List? temp8 = instance.MyList; + temp8 ??= new global::System.Collections.Generic.List(); + BindCore(section6, ref temp8, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp8; } else { instance.MyList = instance.MyList; } - if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section8) + var value9 = configuration.GetSection("MyArray"); + if (AsConfigWithChildren(value9) is IConfigurationSection section10) { - int[]? temp10 = instance.MyArray; - temp10 ??= new int[0]; - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyArray = temp10; + int[]? temp12 = instance.MyArray; + temp12 ??= new int[0]; + BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); + instance.MyArray = temp12; } else { instance.MyArray = instance.MyArray; } + if (instance.MyArray is null && TryGetConfigurationValue(value9, key: null, out string? value13) && value13 == string.Empty) + { + instance.MyArray = global::System.Array.Empty(); + } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + var value14 = configuration.GetSection("MyDictionary"); + if (AsConfigWithChildren(value14) is IConfigurationSection section15) { - global::System.Collections.Generic.Dictionary? temp13 = instance.MyDictionary; - temp13 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp13; + global::System.Collections.Generic.Dictionary? temp17 = instance.MyDictionary; + temp17 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section15, ref temp17, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp17; } else { @@ -191,9 +207,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); - if (configuration["MyInt"] is string value14 && !string.IsNullOrEmpty(value14)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value18)) { - instance.MyInt = ParseInt(value14, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value18)) + { + instance.MyInt = ParseInt(value18, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { @@ -201,6 +220,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -268,7 +299,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue.generated.txt index 02e16bbfddd1de..ad3f7ab95c9472 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue.generated.txt @@ -60,29 +60,39 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration IConfigurationSection section = configuration.GetSection(key); - if (section.Value is not string value) + if (TryGetConfigurationValue(section, key: null, out string? value) && !string.IsNullOrEmpty(value)) { - return null; + if (type == typeof(int)) + { + return ParseInt(value, section.Path); + } + else if (type == typeof(bool?)) + { + return ParseBool(value, section.Path); + } + else if (type == typeof(byte[])) + { + return ParseByteArray(value, section.Path); + } + else if (type == typeof(global::System.Globalization.CultureInfo)) + { + return ParseSystemGlobalizationCultureInfo(value, section.Path); + } } - if (type == typeof(int)) - { - return ParseInt(value, section.Path); - } - else if (type == typeof(bool?)) - { - return ParseBool(value, section.Path); - } - else if (type == typeof(byte[])) - { - return ParseByteArray(value, section.Path); - } - else if (type == typeof(global::System.Globalization.CultureInfo)) + return null; + } + + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) { - return ParseSystemGlobalizationCultureInfo(value, section.Path); + return section.TryGetValue(key, out value); } - - return null; + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; } public static int ParseInt(string value, string? path) @@ -93,7 +103,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } @@ -105,7 +115,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(bool)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(bool)}'.", exception); } } @@ -117,7 +127,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(byte[])}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(byte[])}'.", exception); } } @@ -129,7 +139,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.Globalization.CultureInfo)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.Globalization.CultureInfo)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue_T_Key.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue_T_Key.generated.txt index 1c6888d831b7e2..cd9549063e3b8e 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue_T_Key.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue_T_Key.generated.txt @@ -48,17 +48,27 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration IConfigurationSection section = configuration.GetSection(key); - if (section.Value is not string value) + if (TryGetConfigurationValue(section, key: null, out string? value) && !string.IsNullOrEmpty(value)) { - return null; + if (type == typeof(int)) + { + return ParseInt(value, section.Path); + } } - if (type == typeof(int)) + return null; + } + + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) { - return ParseInt(value, section.Path); + return section.TryGetValue(key, out value); } - - return null; + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; } public static int ParseInt(string value, string? path) @@ -69,7 +79,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue_T_Key_DefaultValue.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue_T_Key_DefaultValue.generated.txt index 63cc9ff7ef3f14..d5830fd0832b44 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue_T_Key_DefaultValue.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue_T_Key_DefaultValue.generated.txt @@ -48,17 +48,27 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration IConfigurationSection section = configuration.GetSection(key); - if (section.Value is not string value) + if (TryGetConfigurationValue(section, key: null, out string? value) && !string.IsNullOrEmpty(value)) { - return null; + if (type == typeof(int)) + { + return ParseInt(value, section.Path); + } } - if (type == typeof(int)) + return null; + } + + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) { - return ParseInt(value, section.Path); + return section.TryGetValue(key, out value); } - - return null; + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; } public static int ParseInt(string value, string? path) @@ -69,7 +79,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue_TypeOf_Key.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue_TypeOf_Key.generated.txt index a0c635eca06b96..975269f002e347 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue_TypeOf_Key.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue_TypeOf_Key.generated.txt @@ -48,17 +48,27 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration IConfigurationSection section = configuration.GetSection(key); - if (section.Value is not string value) + if (TryGetConfigurationValue(section, key: null, out string? value) && !string.IsNullOrEmpty(value)) { - return null; + if (type == typeof(bool?)) + { + return ParseBool(value, section.Path); + } } - if (type == typeof(bool?)) + return null; + } + + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) { - return ParseBool(value, section.Path); + return section.TryGetValue(key, out value); } - - return null; + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; } public static bool ParseBool(string value, string? path) @@ -69,7 +79,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(bool)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(bool)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue_TypeOf_Key_DefaultValue.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue_TypeOf_Key_DefaultValue.generated.txt index 9a1655ddd5beeb..b3f7dd1eefb627 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue_TypeOf_Key_DefaultValue.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/GetValue_TypeOf_Key_DefaultValue.generated.txt @@ -48,17 +48,27 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration IConfigurationSection section = configuration.GetSection(key); - if (section.Value is not string value) + if (TryGetConfigurationValue(section, key: null, out string? value) && !string.IsNullOrEmpty(value)) { - return null; + if (type == typeof(global::System.Globalization.CultureInfo)) + { + return ParseSystemGlobalizationCultureInfo(value, section.Path); + } } - if (type == typeof(global::System.Globalization.CultureInfo)) + return null; + } + + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) { - return ParseSystemGlobalizationCultureInfo(value, section.Path); + return section.TryGetValue(key, out value); } - - return null; + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; } public static global::System.Globalization.CultureInfo ParseSystemGlobalizationCultureInfo(string value, string? path) @@ -69,7 +79,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.Globalization.CultureInfo)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.Globalization.CultureInfo)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_PrimitivesOnly.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_PrimitivesOnly.generated.txt index 97cdab82512c59..2d4f4da184a350 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_PrimitivesOnly.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_PrimitivesOnly.generated.txt @@ -71,9 +71,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { throw new InvalidOperationException(); } - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(configuration, key: null, out string? value)) { - return ParseInt(value, section.Path); + if (!string.IsNullOrEmpty(value)) + { + return ParseInt(value, section.Path); + } } } else if (type == typeof(string)) @@ -82,7 +85,10 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { throw new InvalidOperationException(); } - return section.Value; + if (TryGetConfigurationValue(configuration, key: null, out string? value)) + { + return value; + } } else if (type == typeof(float)) { @@ -90,9 +96,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { throw new InvalidOperationException(); } - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(configuration, key: null, out string? value)) { - return ParseFloat(value, section.Path); + if (!string.IsNullOrEmpty(value)) + { + return ParseFloat(value, section.Path); + } } } else if (type == typeof(double)) @@ -101,15 +110,30 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { throw new InvalidOperationException(); } - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(configuration, key: null, out string? value)) { - return ParseDouble(value, section.Path); + if (!string.IsNullOrEmpty(value)) + { + return ParseDouble(value, section.Path); + } } } throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + public static bool HasValueOrChildren(IConfiguration configuration) { if ((configuration as IConfigurationSection)?.Value is not null) @@ -154,7 +178,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } @@ -166,7 +190,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(float)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(float)}'.", exception); } } @@ -178,7 +202,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(double)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(double)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_T.generated.txt index 1c7b92598f41e8..9fbde7f573ef01 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_T.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_T.generated.txt @@ -70,9 +70,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -83,9 +86,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - temp1.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + temp1.Add(ParseInt(value, section.Path)); + } } } @@ -98,7 +104,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { instance[section.Key] = value; } @@ -109,7 +115,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value2) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value2)) { instance.MyString = value2; } @@ -122,45 +128,55 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value3 && !string.IsNullOrEmpty(value3)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value3)) { - instance.MyInt = ParseInt(value3, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value3)) + { + instance.MyInt = ParseInt(value3, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section4) + var value4 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value4) is IConfigurationSection section5) { - global::System.Collections.Generic.List? temp6 = instance.MyList; - temp6 ??= new global::System.Collections.Generic.List(); - BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp6; + global::System.Collections.Generic.List? temp7 = instance.MyList; + temp7 ??= new global::System.Collections.Generic.List(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp7; } else { instance.MyList = instance.MyList; } - if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section7) + var value8 = configuration.GetSection("MyArray"); + if (AsConfigWithChildren(value8) is IConfigurationSection section9) { - int[]? temp9 = instance.MyArray; - temp9 ??= new int[0]; - BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); - instance.MyArray = temp9; + int[]? temp11 = instance.MyArray; + temp11 ??= new int[0]; + BindCore(section9, ref temp11, defaultValueIfNotFound: false, binderOptions); + instance.MyArray = temp11; } else { instance.MyArray = instance.MyArray; } + if (instance.MyArray is null && TryGetConfigurationValue(value8, key: null, out string? value12) && value12 == string.Empty) + { + instance.MyArray = global::System.Array.Empty(); + } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section10) + var value13 = configuration.GetSection("MyDictionary"); + if (AsConfigWithChildren(value13) is IConfigurationSection section14) { - global::System.Collections.Generic.Dictionary? temp12 = instance.MyDictionary; - temp12 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp12; + global::System.Collections.Generic.Dictionary? temp16 = instance.MyDictionary; + temp16 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section14, ref temp16, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp16; } else { @@ -168,6 +184,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -235,7 +263,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_T_BinderOptions.generated.txt index 7598e236600076..4c5a9b5732038a 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_T_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_T_BinderOptions.generated.txt @@ -70,9 +70,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -83,9 +86,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - temp1.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + temp1.Add(ParseInt(value, section.Path)); + } } } @@ -98,7 +104,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { instance[section.Key] = value; } @@ -109,7 +115,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value2) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value2)) { instance.MyString = value2; } @@ -122,45 +128,55 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value3 && !string.IsNullOrEmpty(value3)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value3)) { - instance.MyInt = ParseInt(value3, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value3)) + { + instance.MyInt = ParseInt(value3, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section4) + var value4 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value4) is IConfigurationSection section5) { - global::System.Collections.Generic.List? temp6 = instance.MyList; - temp6 ??= new global::System.Collections.Generic.List(); - BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp6; + global::System.Collections.Generic.List? temp7 = instance.MyList; + temp7 ??= new global::System.Collections.Generic.List(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp7; } else { instance.MyList = instance.MyList; } - if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section7) + var value8 = configuration.GetSection("MyArray"); + if (AsConfigWithChildren(value8) is IConfigurationSection section9) { - int[]? temp9 = instance.MyArray; - temp9 ??= new int[0]; - BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); - instance.MyArray = temp9; + int[]? temp11 = instance.MyArray; + temp11 ??= new int[0]; + BindCore(section9, ref temp11, defaultValueIfNotFound: false, binderOptions); + instance.MyArray = temp11; } else { instance.MyArray = instance.MyArray; } + if (instance.MyArray is null && TryGetConfigurationValue(value8, key: null, out string? value12) && value12 == string.Empty) + { + instance.MyArray = global::System.Array.Empty(); + } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section10) + var value13 = configuration.GetSection("MyDictionary"); + if (AsConfigWithChildren(value13) is IConfigurationSection section14) { - global::System.Collections.Generic.Dictionary? temp12 = instance.MyDictionary; - temp12 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp12; + global::System.Collections.Generic.Dictionary? temp16 = instance.MyDictionary; + temp16 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section14, ref temp16, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp16; } else { @@ -168,6 +184,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -235,7 +263,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_TypeOf.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_TypeOf.generated.txt index 589f4b4fb10d93..335fd179bc58dc 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_TypeOf.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_TypeOf.generated.txt @@ -70,9 +70,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); - if (configuration["MyInt"] is string value1 && !string.IsNullOrEmpty(value1)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value1)) { - instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value1)) + { + instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { @@ -80,6 +83,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -147,7 +162,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_TypeOf_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_TypeOf_BinderOptions.generated.txt index 53ee49e5ed1c69..95eec86a28cb63 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_TypeOf_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_TypeOf_BinderOptions.generated.txt @@ -70,9 +70,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); - if (configuration["MyInt"] is string value1 && !string.IsNullOrEmpty(value1)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value1)) { - instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value1)) + { + instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { @@ -80,6 +83,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -147,7 +162,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/BindConfiguration.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/BindConfiguration.generated.txt index 7981bfc8de6d56..30a05d9f4593fa 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/BindConfiguration.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/BindConfiguration.generated.txt @@ -101,9 +101,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -112,7 +115,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value1) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value1)) { instance.MyString = value1; } @@ -125,21 +128,25 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value2 && !string.IsNullOrEmpty(value2)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value2)) { - instance.MyInt = ParseInt(value2, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value2)) + { + instance.MyInt = ParseInt(value2, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) + var value3 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value3) is IConfigurationSection section4) { - global::System.Collections.Generic.List? temp5 = instance.MyList; - temp5 ??= new global::System.Collections.Generic.List(); - BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp5; + global::System.Collections.Generic.List? temp6 = instance.MyList; + temp6 ??= new global::System.Collections.Generic.List(); + BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp6; } else { @@ -147,6 +154,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -214,7 +233,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/BindConfigurationWithConfigureActions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/BindConfigurationWithConfigureActions.generated.txt index 7c89f54e229024..8fd2e401d38d13 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/BindConfigurationWithConfigureActions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/BindConfigurationWithConfigureActions.generated.txt @@ -101,9 +101,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -112,7 +115,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value1) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value1)) { instance.MyString = value1; } @@ -125,21 +128,25 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value2 && !string.IsNullOrEmpty(value2)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value2)) { - instance.MyInt = ParseInt(value2, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value2)) + { + instance.MyInt = ParseInt(value2, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) + var value3 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value3) is IConfigurationSection section4) { - global::System.Collections.Generic.List? temp5 = instance.MyList; - temp5 ??= new global::System.Collections.Generic.List(); - BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp5; + global::System.Collections.Generic.List? temp6 = instance.MyList; + temp6 ??= new global::System.Collections.Generic.List(); + BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp6; } else { @@ -147,6 +154,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -214,7 +233,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/Bind_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/Bind_T.generated.txt index d76fb22d50b1ab..f6a67be1dcd641 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/Bind_T.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/Bind_T.generated.txt @@ -107,9 +107,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -118,7 +121,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value1) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value1)) { instance.MyString = value1; } @@ -131,21 +134,25 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value2 && !string.IsNullOrEmpty(value2)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value2)) { - instance.MyInt = ParseInt(value2, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value2)) + { + instance.MyInt = ParseInt(value2, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) + var value3 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value3) is IConfigurationSection section4) { - global::System.Collections.Generic.List? temp5 = instance.MyList; - temp5 ??= new global::System.Collections.Generic.List(); - BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp5; + global::System.Collections.Generic.List? temp6 = instance.MyList; + temp6 ??= new global::System.Collections.Generic.List(); + BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp6; } else { @@ -153,6 +160,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -220,7 +239,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/Bind_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/Bind_T_BinderOptions.generated.txt index 782feb787862fa..f7716c28640037 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/Bind_T_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/Bind_T_BinderOptions.generated.txt @@ -101,9 +101,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -112,7 +115,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value1) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value1)) { instance.MyString = value1; } @@ -125,21 +128,25 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value2 && !string.IsNullOrEmpty(value2)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value2)) { - instance.MyInt = ParseInt(value2, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value2)) + { + instance.MyInt = ParseInt(value2, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) + var value3 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value3) is IConfigurationSection section4) { - global::System.Collections.Generic.List? temp5 = instance.MyList; - temp5 ??= new global::System.Collections.Generic.List(); - BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp5; + global::System.Collections.Generic.List? temp6 = instance.MyList; + temp6 ??= new global::System.Collections.Generic.List(); + BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp6; } else { @@ -147,6 +154,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -214,7 +233,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T.generated.txt index a15b599220ada1..ed219528caa918 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T.generated.txt @@ -95,9 +95,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -106,9 +109,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); - if (configuration["MyInt"] is string value1 && !string.IsNullOrEmpty(value1)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value1)) { - instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value1)) + { + instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { @@ -134,7 +140,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { instance[section.Key] = value; } @@ -145,7 +151,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value3) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value3)) { instance.MyString = value3; } @@ -158,45 +164,51 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value4 && !string.IsNullOrEmpty(value4)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value4)) { - instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value4)) + { + instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + var value5 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value5) is IConfigurationSection section6) { - global::System.Collections.Generic.List? temp7 = instance.MyList; - temp7 ??= new global::System.Collections.Generic.List(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp7; + global::System.Collections.Generic.List? temp8 = instance.MyList; + temp8 ??= new global::System.Collections.Generic.List(); + BindCore(section6, ref temp8, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp8; } else { instance.MyList = instance.MyList; } - if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) + var value9 = configuration.GetSection("MyList2"); + if (AsConfigWithChildren(value9) is IConfigurationSection section10) { - global::System.Collections.Generic.List? temp10 = instance.MyList2; - temp10 ??= new global::System.Collections.Generic.List(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyList2 = temp10; + global::System.Collections.Generic.List? temp12 = instance.MyList2; + temp12 ??= new global::System.Collections.Generic.List(); + BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp12; } else { instance.MyList2 = instance.MyList2; } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + var value13 = configuration.GetSection("MyDictionary"); + if (AsConfigWithChildren(value13) is IConfigurationSection section14) { - global::System.Collections.Generic.Dictionary? temp13 = instance.MyDictionary; - temp13 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp13; + global::System.Collections.Generic.Dictionary? temp16 = instance.MyDictionary; + temp16 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section14, ref temp16, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp16; } else { @@ -204,6 +216,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -271,7 +295,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_BinderOptions.generated.txt index 3e65d96e3f47bb..e2f4bd350bad5f 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_BinderOptions.generated.txt @@ -95,9 +95,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -106,9 +109,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); - if (configuration["MyInt"] is string value1 && !string.IsNullOrEmpty(value1)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value1)) { - instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value1)) + { + instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { @@ -134,7 +140,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { instance[section.Key] = value; } @@ -145,7 +151,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value3) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value3)) { instance.MyString = value3; } @@ -158,45 +164,51 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value4 && !string.IsNullOrEmpty(value4)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value4)) { - instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value4)) + { + instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + var value5 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value5) is IConfigurationSection section6) { - global::System.Collections.Generic.List? temp7 = instance.MyList; - temp7 ??= new global::System.Collections.Generic.List(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp7; + global::System.Collections.Generic.List? temp8 = instance.MyList; + temp8 ??= new global::System.Collections.Generic.List(); + BindCore(section6, ref temp8, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp8; } else { instance.MyList = instance.MyList; } - if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) + var value9 = configuration.GetSection("MyList2"); + if (AsConfigWithChildren(value9) is IConfigurationSection section10) { - global::System.Collections.Generic.List? temp10 = instance.MyList2; - temp10 ??= new global::System.Collections.Generic.List(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyList2 = temp10; + global::System.Collections.Generic.List? temp12 = instance.MyList2; + temp12 ??= new global::System.Collections.Generic.List(); + BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp12; } else { instance.MyList2 = instance.MyList2; } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + var value13 = configuration.GetSection("MyDictionary"); + if (AsConfigWithChildren(value13) is IConfigurationSection section14) { - global::System.Collections.Generic.Dictionary? temp13 = instance.MyDictionary; - temp13 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp13; + global::System.Collections.Generic.Dictionary? temp16 = instance.MyDictionary; + temp16 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section14, ref temp16, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp16; } else { @@ -204,6 +216,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -271,7 +295,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_name.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_name.generated.txt index af7456240056c0..6490fc6d90eceb 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_name.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_name.generated.txt @@ -95,9 +95,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -106,9 +109,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); - if (configuration["MyInt"] is string value1 && !string.IsNullOrEmpty(value1)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value1)) { - instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value1)) + { + instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { @@ -134,7 +140,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { instance[section.Key] = value; } @@ -145,7 +151,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value3) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value3)) { instance.MyString = value3; } @@ -158,45 +164,51 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value4 && !string.IsNullOrEmpty(value4)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value4)) { - instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value4)) + { + instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + var value5 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value5) is IConfigurationSection section6) { - global::System.Collections.Generic.List? temp7 = instance.MyList; - temp7 ??= new global::System.Collections.Generic.List(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp7; + global::System.Collections.Generic.List? temp8 = instance.MyList; + temp8 ??= new global::System.Collections.Generic.List(); + BindCore(section6, ref temp8, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp8; } else { instance.MyList = instance.MyList; } - if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) + var value9 = configuration.GetSection("MyList2"); + if (AsConfigWithChildren(value9) is IConfigurationSection section10) { - global::System.Collections.Generic.List? temp10 = instance.MyList2; - temp10 ??= new global::System.Collections.Generic.List(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyList2 = temp10; + global::System.Collections.Generic.List? temp12 = instance.MyList2; + temp12 ??= new global::System.Collections.Generic.List(); + BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp12; } else { instance.MyList2 = instance.MyList2; } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + var value13 = configuration.GetSection("MyDictionary"); + if (AsConfigWithChildren(value13) is IConfigurationSection section14) { - global::System.Collections.Generic.Dictionary? temp13 = instance.MyDictionary; - temp13 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp13; + global::System.Collections.Generic.Dictionary? temp16 = instance.MyDictionary; + temp16 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section14, ref temp16, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp16; } else { @@ -204,6 +216,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -271,7 +295,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_name_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_name_BinderOptions.generated.txt index b5f3bd2069baa4..d3abf7baf31cb4 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_name_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_name_BinderOptions.generated.txt @@ -89,9 +89,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -100,9 +103,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); - if (configuration["MyInt"] is string value1 && !string.IsNullOrEmpty(value1)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value1)) { - instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value1)) + { + instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { @@ -128,7 +134,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { instance[section.Key] = value; } @@ -139,7 +145,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value3) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value3)) { instance.MyString = value3; } @@ -152,45 +158,51 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value4 && !string.IsNullOrEmpty(value4)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value4)) { - instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value4)) + { + instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + var value5 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value5) is IConfigurationSection section6) { - global::System.Collections.Generic.List? temp7 = instance.MyList; - temp7 ??= new global::System.Collections.Generic.List(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp7; + global::System.Collections.Generic.List? temp8 = instance.MyList; + temp8 ??= new global::System.Collections.Generic.List(); + BindCore(section6, ref temp8, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp8; } else { instance.MyList = instance.MyList; } - if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) + var value9 = configuration.GetSection("MyList2"); + if (AsConfigWithChildren(value9) is IConfigurationSection section10) { - global::System.Collections.Generic.List? temp10 = instance.MyList2; - temp10 ??= new global::System.Collections.Generic.List(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyList2 = temp10; + global::System.Collections.Generic.List? temp12 = instance.MyList2; + temp12 ??= new global::System.Collections.Generic.List(); + BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp12; } else { instance.MyList2 = instance.MyList2; } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + var value13 = configuration.GetSection("MyDictionary"); + if (AsConfigWithChildren(value13) is IConfigurationSection section14) { - global::System.Collections.Generic.Dictionary? temp13 = instance.MyDictionary; - temp13 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp13; + global::System.Collections.Generic.Dictionary? temp16 = instance.MyDictionary; + temp16 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section14, ref temp16, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp16; } else { @@ -198,6 +210,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -265,7 +289,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/Collections.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/Collections.generated.txt index 3bb04809ead380..d97aa128e8d688 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/Collections.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/Collections.generated.txt @@ -71,9 +71,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance[section.Key] = ParseInt(value, section.Path); + if (!string.IsNullOrEmpty(value)) + { + instance[section.Key] = ParseInt(value, section.Path); + } } } } @@ -82,7 +85,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { instance.Add(value); } @@ -98,9 +101,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - temp.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + temp.Add(ParseInt(value, section.Path)); + } } } } @@ -114,9 +120,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - temp[section.Key] = ParseInt(value, section.Path); + if (!string.IsNullOrEmpty(value)) + { + temp[section.Key] = ParseInt(value, section.Path); + } } } } @@ -127,7 +136,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { temp.Add(value); } @@ -138,61 +147,66 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClassWithCustomCollections), s_configKeys_ProgramMyClassWithCustomCollections, configuration, binderOptions); - if (AsConfigWithChildren(configuration.GetSection("CustomDictionary")) is IConfigurationSection section1) + var value1 = configuration.GetSection("CustomDictionary"); + if (AsConfigWithChildren(value1) is IConfigurationSection section2) { - global::Program.CustomDictionary? temp3 = instance.CustomDictionary; - temp3 ??= new global::Program.CustomDictionary(); - BindCore(section1, ref temp3, defaultValueIfNotFound: false, binderOptions); - instance.CustomDictionary = temp3; + global::Program.CustomDictionary? temp4 = instance.CustomDictionary; + temp4 ??= new global::Program.CustomDictionary(); + BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); + instance.CustomDictionary = temp4; } else { instance.CustomDictionary = instance.CustomDictionary; } - if (AsConfigWithChildren(configuration.GetSection("CustomList")) is IConfigurationSection section4) + var value5 = configuration.GetSection("CustomList"); + if (AsConfigWithChildren(value5) is IConfigurationSection section6) { - global::Program.CustomList? temp6 = instance.CustomList; - temp6 ??= new global::Program.CustomList(); - BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); - instance.CustomList = temp6; + global::Program.CustomList? temp8 = instance.CustomList; + temp8 ??= new global::Program.CustomList(); + BindCore(section6, ref temp8, defaultValueIfNotFound: false, binderOptions); + instance.CustomList = temp8; } else { instance.CustomList = instance.CustomList; } - if (AsConfigWithChildren(configuration.GetSection("IReadOnlyList")) is IConfigurationSection section7) + var value9 = configuration.GetSection("IReadOnlyList"); + if (AsConfigWithChildren(value9) is IConfigurationSection section10) { - global::System.Collections.Generic.IReadOnlyList? temp9 = instance.IReadOnlyList; - temp9 = temp9 is null ? (global::System.Collections.Generic.IReadOnlyList)new List() : (global::System.Collections.Generic.IReadOnlyList)new List(temp9); - BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); - instance.IReadOnlyList = temp9; + global::System.Collections.Generic.IReadOnlyList? temp12 = instance.IReadOnlyList; + temp12 = temp12 is null ? (global::System.Collections.Generic.IReadOnlyList)new List() : (global::System.Collections.Generic.IReadOnlyList)new List(temp12); + BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); + instance.IReadOnlyList = temp12; } else { instance.IReadOnlyList = instance.IReadOnlyList; } - if (AsConfigWithChildren(configuration.GetSection("IReadOnlyDictionary")) is IConfigurationSection section10) + var value13 = configuration.GetSection("IReadOnlyDictionary"); + if (AsConfigWithChildren(value13) is IConfigurationSection section14) { - global::System.Collections.Generic.IReadOnlyDictionary? temp12 = instance.IReadOnlyDictionary; - temp12 = temp12 is null ? (global::System.Collections.Generic.IReadOnlyDictionary)new Dictionary() : (global::System.Collections.Generic.IReadOnlyDictionary)temp12.ToDictionary(pair => pair.Key, pair => pair.Value); - BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); - instance.IReadOnlyDictionary = temp12; + global::System.Collections.Generic.IReadOnlyDictionary? temp16 = instance.IReadOnlyDictionary; + temp16 = temp16 is null ? (global::System.Collections.Generic.IReadOnlyDictionary)new Dictionary() : (global::System.Collections.Generic.IReadOnlyDictionary)temp16.ToDictionary(pair => pair.Key, pair => pair.Value); + BindCore(section14, ref temp16, defaultValueIfNotFound: false, binderOptions); + instance.IReadOnlyDictionary = temp16; } else { instance.IReadOnlyDictionary = instance.IReadOnlyDictionary; } - if (AsConfigWithChildren(configuration.GetSection("CollectionStructExplicit")) is IConfigurationSection section13) + var value17 = configuration.GetSection("CollectionStructExplicit"); + if (AsConfigWithChildren(value17) is IConfigurationSection section18) { - global::Program.CollectionStructExplicit temp14 = instance.CollectionStructExplicit; - var temp15 = new global::Program.CollectionStructExplicit(); - BindCore(section13, ref temp15, defaultValueIfNotFound: false, binderOptions); - instance.CollectionStructExplicit = temp15; - temp14 = temp15; + global::Program.CollectionStructExplicit temp19 = instance.CollectionStructExplicit; + var temp20 = new global::Program.CollectionStructExplicit(); + BindCore(section18, ref temp20, defaultValueIfNotFound: false, binderOptions); + instance.CollectionStructExplicit = temp20; + temp19 = temp20; } else { @@ -200,6 +214,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -267,7 +293,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/DefaultConstructorParameters.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/DefaultConstructorParameters.generated.txt index bf84547cab0615..285b5f782a58bb 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/DefaultConstructorParameters.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/DefaultConstructorParameters.generated.txt @@ -64,92 +64,182 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static global::Program.ClassWhereParametersHaveDefaultValue InitializeProgramClassWhereParametersHaveDefaultValue(IConfiguration configuration, BinderOptions? binderOptions) { string name = "John Doe"!; - if (configuration["Name"] is string value0) + if (TryGetConfigurationValue(configuration, key: "Name", out string? value0)) { name = value0; } string address = "1 Microsoft Way"!; - if (configuration["Address"] is string value1) + if (TryGetConfigurationValue(configuration, key: "Address", out string? value1)) { address = value1; } int age = (int)(42); - if (configuration["Age"] is string value2 && !string.IsNullOrEmpty(value2)) + if (TryGetConfigurationValue(configuration, key: "Age", out string? value2)) { - age = ParseInt(value2, configuration.GetSection("Age").Path); + if (!string.IsNullOrEmpty(value2)) + { + age = ParseInt(value2, configuration.GetSection("Age").Path); + } } float f = 42F; - if (configuration["F"] is string value3 && !string.IsNullOrEmpty(value3)) + if (TryGetConfigurationValue(configuration, key: "F", out string? value3)) { - f = ParseFloat(value3, configuration.GetSection("F").Path); + if (!string.IsNullOrEmpty(value3)) + { + f = ParseFloat(value3, configuration.GetSection("F").Path); + } } double d = 3.1415899999999999D; - if (configuration["D"] is string value4 && !string.IsNullOrEmpty(value4)) + if (TryGetConfigurationValue(configuration, key: "D", out string? value4)) { - d = ParseDouble(value4, configuration.GetSection("D").Path); + if (!string.IsNullOrEmpty(value4)) + { + d = ParseDouble(value4, configuration.GetSection("D").Path); + } } decimal m = 3.1415926535897932384626433M; - if (configuration["M"] is string value5 && !string.IsNullOrEmpty(value5)) + if (TryGetConfigurationValue(configuration, key: "M", out string? value5)) { - m = ParseDecimal(value5, configuration.GetSection("M").Path); + if (!string.IsNullOrEmpty(value5)) + { + m = ParseDecimal(value5, configuration.GetSection("M").Path); + } } global::System.StringComparison sc = (global::System.StringComparison)(4); - if (configuration["SC"] is string value6 && !string.IsNullOrEmpty(value6)) + if (TryGetConfigurationValue(configuration, key: "SC", out string? value6)) { - sc = ParseEnum(value6, configuration.GetSection("SC").Path); + if (!string.IsNullOrEmpty(value6)) + { + sc = ParseEnum(value6, configuration.GetSection("SC").Path); + } } char c = 'q'; - if (configuration["C"] is string value7 && !string.IsNullOrEmpty(value7)) + if (TryGetConfigurationValue(configuration, key: "C", out string? value7)) { - c = ParseChar(value7, configuration.GetSection("C").Path); + if (!string.IsNullOrEmpty(value7)) + { + c = ParseChar(value7, configuration.GetSection("C").Path); + } } int? nage = (int?)(42); - if (configuration["NAge"] is string value8 && !string.IsNullOrEmpty(value8)) + if (TryGetConfigurationValue(configuration, key: "NAge", out string? value8)) { - nage = ParseInt(value8, configuration.GetSection("NAge").Path); + if (value8 is null) + { + nage = null; + } + else + { + if (!string.IsNullOrEmpty(value8)) + { + nage = ParseInt(value8, configuration.GetSection("NAge").Path); + } + } } float? nf = 42F; - if (configuration["NF"] is string value9 && !string.IsNullOrEmpty(value9)) + if (TryGetConfigurationValue(configuration, key: "NF", out string? value9)) { - nf = ParseFloat(value9, configuration.GetSection("NF").Path); + if (value9 is null) + { + nf = null; + } + else + { + if (!string.IsNullOrEmpty(value9)) + { + nf = ParseFloat(value9, configuration.GetSection("NF").Path); + } + } } double? nd = 3.1415899999999999D; - if (configuration["ND"] is string value10 && !string.IsNullOrEmpty(value10)) + if (TryGetConfigurationValue(configuration, key: "ND", out string? value10)) { - nd = ParseDouble(value10, configuration.GetSection("ND").Path); + if (value10 is null) + { + nd = null; + } + else + { + if (!string.IsNullOrEmpty(value10)) + { + nd = ParseDouble(value10, configuration.GetSection("ND").Path); + } + } } decimal? nm = 3.1415926535897932384626433M; - if (configuration["NM"] is string value11 && !string.IsNullOrEmpty(value11)) + if (TryGetConfigurationValue(configuration, key: "NM", out string? value11)) { - nm = ParseDecimal(value11, configuration.GetSection("NM").Path); + if (value11 is null) + { + nm = null; + } + else + { + if (!string.IsNullOrEmpty(value11)) + { + nm = ParseDecimal(value11, configuration.GetSection("NM").Path); + } + } } global::System.StringComparison? nsc = (global::System.StringComparison?)(4); - if (configuration["NSC"] is string value12 && !string.IsNullOrEmpty(value12)) + if (TryGetConfigurationValue(configuration, key: "NSC", out string? value12)) { - nsc = ParseEnum(value12, configuration.GetSection("NSC").Path); + if (value12 is null) + { + nsc = null; + } + else + { + if (!string.IsNullOrEmpty(value12)) + { + nsc = ParseEnum(value12, configuration.GetSection("NSC").Path); + } + } } char? nc = 'q'; - if (configuration["NC"] is string value13 && !string.IsNullOrEmpty(value13)) + if (TryGetConfigurationValue(configuration, key: "NC", out string? value13)) { - nc = ParseChar(value13, configuration.GetSection("NC").Path); + if (value13 is null) + { + nc = null; + } + else + { + if (!string.IsNullOrEmpty(value13)) + { + nc = ParseChar(value13, configuration.GetSection("NC").Path); + } + } } return new global::Program.ClassWhereParametersHaveDefaultValue(name, address, age, f, d, m, sc, c, nage, nf, nd, nm, nsc, nc); } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -181,7 +271,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(T)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(T)}'.", exception); } } @@ -193,7 +283,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } @@ -205,7 +295,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(float)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(float)}'.", exception); } } @@ -217,7 +307,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(double)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(double)}'.", exception); } } @@ -229,7 +319,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(decimal)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(decimal)}'.", exception); } } @@ -241,7 +331,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(char)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(char)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/EmptyConfigType.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/EmptyConfigType.generated.txt index b9ca5b5e42e500..56e3b5dffb1b21 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/EmptyConfigType.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/EmptyConfigType.generated.txt @@ -67,12 +67,25 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::TypeWithNoMembers_Wrapper), s_configKeys_TypeWithNoMembers_Wrapper, configuration, binderOptions); - if (AsConfigWithChildren(configuration.GetSection("Member")) is IConfigurationSection section0) + var value0 = configuration.GetSection("Member"); + if (AsConfigWithChildren(value0) is IConfigurationSection section1) { instance.Member ??= new global::TypeWithNoMembers(); } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/Primitives.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/Primitives.generated.txt index 862e062c054995..506c557f1a1840 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/Primitives.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/Primitives.generated.txt @@ -60,52 +60,67 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["Prop0"] is string value0 && !string.IsNullOrEmpty(value0)) + if (TryGetConfigurationValue(configuration, key: "Prop0", out string? value0)) { - instance.Prop0 = ParseBool(value0, configuration.GetSection("Prop0").Path); + if (!string.IsNullOrEmpty(value0)) + { + instance.Prop0 = ParseBool(value0, configuration.GetSection("Prop0").Path); + } } else if (defaultValueIfNotFound) { instance.Prop0 = instance.Prop0; } - if (configuration["Prop1"] is string value1 && !string.IsNullOrEmpty(value1)) + if (TryGetConfigurationValue(configuration, key: "Prop1", out string? value1)) { - instance.Prop1 = ParseByte(value1, configuration.GetSection("Prop1").Path); + if (!string.IsNullOrEmpty(value1)) + { + instance.Prop1 = ParseByte(value1, configuration.GetSection("Prop1").Path); + } } else if (defaultValueIfNotFound) { instance.Prop1 = instance.Prop1; } - if (configuration["Prop2"] is string value2 && !string.IsNullOrEmpty(value2)) + if (TryGetConfigurationValue(configuration, key: "Prop2", out string? value2)) { - instance.Prop2 = ParseSbyte(value2, configuration.GetSection("Prop2").Path); + if (!string.IsNullOrEmpty(value2)) + { + instance.Prop2 = ParseSbyte(value2, configuration.GetSection("Prop2").Path); + } } else if (defaultValueIfNotFound) { instance.Prop2 = instance.Prop2; } - if (configuration["Prop3"] is string value3 && !string.IsNullOrEmpty(value3)) + if (TryGetConfigurationValue(configuration, key: "Prop3", out string? value3)) { - instance.Prop3 = ParseChar(value3, configuration.GetSection("Prop3").Path); + if (!string.IsNullOrEmpty(value3)) + { + instance.Prop3 = ParseChar(value3, configuration.GetSection("Prop3").Path); + } } else if (defaultValueIfNotFound) { instance.Prop3 = instance.Prop3; } - if (configuration["Prop4"] is string value4 && !string.IsNullOrEmpty(value4)) + if (TryGetConfigurationValue(configuration, key: "Prop4", out string? value4)) { - instance.Prop4 = ParseDouble(value4, configuration.GetSection("Prop4").Path); + if (!string.IsNullOrEmpty(value4)) + { + instance.Prop4 = ParseDouble(value4, configuration.GetSection("Prop4").Path); + } } else if (defaultValueIfNotFound) { instance.Prop4 = instance.Prop4; } - if (configuration["Prop5"] is string value5) + if (TryGetConfigurationValue(configuration, key: "Prop5", out string? value5)) { instance.Prop5 = value5; } @@ -118,70 +133,91 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["Prop6"] is string value6 && !string.IsNullOrEmpty(value6)) + if (TryGetConfigurationValue(configuration, key: "Prop6", out string? value6)) { - instance.Prop6 = ParseInt(value6, configuration.GetSection("Prop6").Path); + if (!string.IsNullOrEmpty(value6)) + { + instance.Prop6 = ParseInt(value6, configuration.GetSection("Prop6").Path); + } } else if (defaultValueIfNotFound) { instance.Prop6 = instance.Prop6; } - if (configuration["Prop8"] is string value7 && !string.IsNullOrEmpty(value7)) + if (TryGetConfigurationValue(configuration, key: "Prop8", out string? value7)) { - instance.Prop8 = ParseShort(value7, configuration.GetSection("Prop8").Path); + if (!string.IsNullOrEmpty(value7)) + { + instance.Prop8 = ParseShort(value7, configuration.GetSection("Prop8").Path); + } } else if (defaultValueIfNotFound) { instance.Prop8 = instance.Prop8; } - if (configuration["Prop9"] is string value8 && !string.IsNullOrEmpty(value8)) + if (TryGetConfigurationValue(configuration, key: "Prop9", out string? value8)) { - instance.Prop9 = ParseLong(value8, configuration.GetSection("Prop9").Path); + if (!string.IsNullOrEmpty(value8)) + { + instance.Prop9 = ParseLong(value8, configuration.GetSection("Prop9").Path); + } } else if (defaultValueIfNotFound) { instance.Prop9 = instance.Prop9; } - if (configuration["Prop10"] is string value9 && !string.IsNullOrEmpty(value9)) + if (TryGetConfigurationValue(configuration, key: "Prop10", out string? value9)) { - instance.Prop10 = ParseFloat(value9, configuration.GetSection("Prop10").Path); + if (!string.IsNullOrEmpty(value9)) + { + instance.Prop10 = ParseFloat(value9, configuration.GetSection("Prop10").Path); + } } else if (defaultValueIfNotFound) { instance.Prop10 = instance.Prop10; } - if (configuration["Prop13"] is string value10 && !string.IsNullOrEmpty(value10)) + if (TryGetConfigurationValue(configuration, key: "Prop13", out string? value10)) { - instance.Prop13 = ParseUshort(value10, configuration.GetSection("Prop13").Path); + if (!string.IsNullOrEmpty(value10)) + { + instance.Prop13 = ParseUshort(value10, configuration.GetSection("Prop13").Path); + } } else if (defaultValueIfNotFound) { instance.Prop13 = instance.Prop13; } - if (configuration["Prop14"] is string value11 && !string.IsNullOrEmpty(value11)) + if (TryGetConfigurationValue(configuration, key: "Prop14", out string? value11)) { - instance.Prop14 = ParseUint(value11, configuration.GetSection("Prop14").Path); + if (!string.IsNullOrEmpty(value11)) + { + instance.Prop14 = ParseUint(value11, configuration.GetSection("Prop14").Path); + } } else if (defaultValueIfNotFound) { instance.Prop14 = instance.Prop14; } - if (configuration["Prop15"] is string value12 && !string.IsNullOrEmpty(value12)) + if (TryGetConfigurationValue(configuration, key: "Prop15", out string? value12)) { - instance.Prop15 = ParseUlong(value12, configuration.GetSection("Prop15").Path); + if (!string.IsNullOrEmpty(value12)) + { + instance.Prop15 = ParseUlong(value12, configuration.GetSection("Prop15").Path); + } } else if (defaultValueIfNotFound) { instance.Prop15 = instance.Prop15; } - if (configuration["Prop16"] is string value13) + if (TryGetConfigurationValue(configuration, key: "Prop16", out string? value13)) { instance.Prop16 = value13; } @@ -194,9 +230,19 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["Prop17"] is string value14 && !string.IsNullOrEmpty(value14)) + if (TryGetConfigurationValue(configuration, key: "Prop17", out string? value14)) { - instance.Prop17 = ParseSystemGlobalizationCultureInfo(value14, configuration.GetSection("Prop17").Path); + if (value14 is null) + { + instance.Prop17 = null; + } + else + { + if (!string.IsNullOrEmpty(value14)) + { + instance.Prop17 = ParseSystemGlobalizationCultureInfo(value14, configuration.GetSection("Prop17").Path); + } + } } else if (defaultValueIfNotFound) { @@ -207,54 +253,79 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["Prop19"] is string value15 && !string.IsNullOrEmpty(value15)) + if (TryGetConfigurationValue(configuration, key: "Prop19", out string? value15)) { - instance.Prop19 = ParseSystemDateTime(value15, configuration.GetSection("Prop19").Path); + if (!string.IsNullOrEmpty(value15)) + { + instance.Prop19 = ParseSystemDateTime(value15, configuration.GetSection("Prop19").Path); + } } else if (defaultValueIfNotFound) { instance.Prop19 = instance.Prop19; } - if (configuration["Prop20"] is string value16 && !string.IsNullOrEmpty(value16)) + if (TryGetConfigurationValue(configuration, key: "Prop20", out string? value16)) { - instance.Prop20 = ParseSystemDateTimeOffset(value16, configuration.GetSection("Prop20").Path); + if (!string.IsNullOrEmpty(value16)) + { + instance.Prop20 = ParseSystemDateTimeOffset(value16, configuration.GetSection("Prop20").Path); + } } else if (defaultValueIfNotFound) { instance.Prop20 = instance.Prop20; } - if (configuration["Prop21"] is string value17 && !string.IsNullOrEmpty(value17)) + if (TryGetConfigurationValue(configuration, key: "Prop21", out string? value17)) { - instance.Prop21 = ParseDecimal(value17, configuration.GetSection("Prop21").Path); + if (!string.IsNullOrEmpty(value17)) + { + instance.Prop21 = ParseDecimal(value17, configuration.GetSection("Prop21").Path); + } } else if (defaultValueIfNotFound) { instance.Prop21 = instance.Prop21; } - if (configuration["Prop23"] is string value18 && !string.IsNullOrEmpty(value18)) + if (TryGetConfigurationValue(configuration, key: "Prop23", out string? value18)) { - instance.Prop23 = ParseSystemTimeSpan(value18, configuration.GetSection("Prop23").Path); + if (!string.IsNullOrEmpty(value18)) + { + instance.Prop23 = ParseSystemTimeSpan(value18, configuration.GetSection("Prop23").Path); + } } else if (defaultValueIfNotFound) { instance.Prop23 = instance.Prop23; } - if (configuration["Prop24"] is string value19 && !string.IsNullOrEmpty(value19)) + if (TryGetConfigurationValue(configuration, key: "Prop24", out string? value19)) { - instance.Prop24 = ParseSystemGuid(value19, configuration.GetSection("Prop24").Path); + if (!string.IsNullOrEmpty(value19)) + { + instance.Prop24 = ParseSystemGuid(value19, configuration.GetSection("Prop24").Path); + } } else if (defaultValueIfNotFound) { instance.Prop24 = instance.Prop24; } - if (configuration["Prop25"] is string value20 && !string.IsNullOrEmpty(value20)) + if (TryGetConfigurationValue(configuration, key: "Prop25", out string? value20)) { - instance.Prop25 = ParseSystemUri(value20, configuration.GetSection("Prop25").Path); + if (value20 is null) + { + instance.Prop25 = null; + } + else + { + if (!string.IsNullOrEmpty(value20)) + { + instance.Prop25 = ParseSystemUri(value20, configuration.GetSection("Prop25").Path); + } + } } else if (defaultValueIfNotFound) { @@ -265,9 +336,19 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["Prop26"] is string value21 && !string.IsNullOrEmpty(value21)) + if (TryGetConfigurationValue(configuration, key: "Prop26", out string? value21)) { - instance.Prop26 = ParseSystemVersion(value21, configuration.GetSection("Prop26").Path); + if (value21 is null) + { + instance.Prop26 = null; + } + else + { + if (!string.IsNullOrEmpty(value21)) + { + instance.Prop26 = ParseSystemVersion(value21, configuration.GetSection("Prop26").Path); + } + } } else if (defaultValueIfNotFound) { @@ -278,18 +359,35 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["Prop27"] is string value22 && !string.IsNullOrEmpty(value22)) + if (TryGetConfigurationValue(configuration, key: "Prop27", out string? value22)) { - instance.Prop27 = ParseEnum(value22, configuration.GetSection("Prop27").Path); + if (!string.IsNullOrEmpty(value22)) + { + instance.Prop27 = ParseEnum(value22, configuration.GetSection("Prop27").Path); + } } else if (defaultValueIfNotFound) { instance.Prop27 = instance.Prop27; } - if (configuration["Prop28"] is string value23 && !string.IsNullOrEmpty(value23)) + if (TryGetConfigurationValue(configuration, key: "Prop28", out string? value23)) { - instance.Prop28 = ParseByteArray(value23, configuration.GetSection("Prop28").Path); + if (value23 is null) + { + instance.Prop28 = null; + } + else + { + if (value23 == string.Empty) + { + instance.Prop28 = ParseByteArray(value23, configuration.GetSection("Prop28").Path); + } + else if (!string.IsNullOrEmpty(value23)) + { + instance.Prop28 = ParseByteArray(value23, configuration.GetSection("Prop28").Path); + } + } } else if (defaultValueIfNotFound) { @@ -300,18 +398,24 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["Prop29"] is string value24 && !string.IsNullOrEmpty(value24)) + if (TryGetConfigurationValue(configuration, key: "Prop29", out string? value24)) { - instance.Prop29 = ParseInt(value24, configuration.GetSection("Prop29").Path); + if (!string.IsNullOrEmpty(value24)) + { + instance.Prop29 = ParseInt(value24, configuration.GetSection("Prop29").Path); + } } else if (defaultValueIfNotFound) { instance.Prop29 = instance.Prop29; } - if (configuration["Prop30"] is string value25 && !string.IsNullOrEmpty(value25)) + if (TryGetConfigurationValue(configuration, key: "Prop30", out string? value25)) { - instance.Prop30 = ParseSystemDateTime(value25, configuration.GetSection("Prop30").Path); + if (!string.IsNullOrEmpty(value25)) + { + instance.Prop30 = ParseSystemDateTime(value25, configuration.GetSection("Prop30").Path); + } } else if (defaultValueIfNotFound) { @@ -319,6 +423,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -350,7 +466,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(T)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(T)}'.", exception); } } @@ -362,7 +478,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(bool)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(bool)}'.", exception); } } @@ -374,7 +490,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(byte)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(byte)}'.", exception); } } @@ -386,7 +502,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(sbyte)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(sbyte)}'.", exception); } } @@ -398,7 +514,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(char)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(char)}'.", exception); } } @@ -410,7 +526,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(double)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(double)}'.", exception); } } @@ -422,7 +538,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } @@ -434,7 +550,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(short)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(short)}'.", exception); } } @@ -446,7 +562,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(long)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(long)}'.", exception); } } @@ -458,7 +574,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(float)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(float)}'.", exception); } } @@ -470,7 +586,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(ushort)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(ushort)}'.", exception); } } @@ -482,7 +598,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(uint)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(uint)}'.", exception); } } @@ -494,7 +610,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(ulong)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(ulong)}'.", exception); } } @@ -506,7 +622,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.Globalization.CultureInfo)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.Globalization.CultureInfo)}'.", exception); } } @@ -518,7 +634,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.DateTime)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.DateTime)}'.", exception); } } @@ -530,7 +646,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.DateTimeOffset)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.DateTimeOffset)}'.", exception); } } @@ -542,7 +658,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(decimal)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(decimal)}'.", exception); } } @@ -554,7 +670,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.TimeSpan)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.TimeSpan)}'.", exception); } } @@ -566,7 +682,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.Guid)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.Guid)}'.", exception); } } @@ -578,7 +694,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.Uri)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.Uri)}'.", exception); } } @@ -590,7 +706,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.Version)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.Version)}'.", exception); } } @@ -602,7 +718,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(byte[])}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(byte[])}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/UnsupportedTypes.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/UnsupportedTypes.generated.txt index 6b9ae8a88e90f7..2fb36106308427 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/UnsupportedTypes.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/UnsupportedTypes.generated.txt @@ -97,7 +97,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { instance.Add(value); } @@ -110,7 +110,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { temp1.Add(value); } @@ -130,7 +130,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Options), s_configKeys_Options, configuration, binderOptions); - if (configuration["Name"] is string value2) + if (TryGetConfigurationValue(configuration, key: "Name", out string? value2)) { instance.Name = value2; } @@ -143,45 +143,55 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["Age"] is string value3 && !string.IsNullOrEmpty(value3)) + if (TryGetConfigurationValue(configuration, key: "Age", out string? value3)) { - instance.Age = ParseInt(value3, configuration.GetSection("Age").Path); + if (!string.IsNullOrEmpty(value3)) + { + instance.Age = ParseInt(value3, configuration.GetSection("Age").Path); + } } else if (defaultValueIfNotFound) { instance.Age = instance.Age; } - if (AsConfigWithChildren(configuration.GetSection("List")) is IConfigurationSection section4) + var value4 = configuration.GetSection("List"); + if (AsConfigWithChildren(value4) is IConfigurationSection section5) { - global::System.Collections.Generic.List? temp6 = instance.List; - temp6 ??= new global::System.Collections.Generic.List(); - BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); - instance.List = temp6; + global::System.Collections.Generic.List? temp7 = instance.List; + temp7 ??= new global::System.Collections.Generic.List(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.List = temp7; } else { instance.List = instance.List; } - if (AsConfigWithChildren(configuration.GetSection("Array")) is IConfigurationSection section7) + var value8 = configuration.GetSection("Array"); + if (AsConfigWithChildren(value8) is IConfigurationSection section9) { - string[]? temp9 = instance.Array; - temp9 ??= new string[0]; - BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); - instance.Array = temp9; + string[]? temp11 = instance.Array; + temp11 ??= new string[0]; + BindCore(section9, ref temp11, defaultValueIfNotFound: false, binderOptions); + instance.Array = temp11; } else { instance.Array = instance.Array; } + if (instance.Array is null && TryGetConfigurationValue(value8, key: null, out string? value12) && value12 == string.Empty) + { + instance.Array = global::System.Array.Empty(); + } - if (AsConfigWithChildren(configuration.GetSection("Record")) is IConfigurationSection section10) + var value13 = configuration.GetSection("Record"); + if (AsConfigWithChildren(value13) is IConfigurationSection section14) { - global::Record? temp12 = instance.Record; - temp12 ??= InitializeRecordAction(section10, binderOptions); - BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); - instance.Record = temp12; + global::Record? temp16 = instance.Record; + temp16 ??= InitializeRecordAction(section14, binderOptions); + BindCore(section14, ref temp16, defaultValueIfNotFound: false, binderOptions); + instance.Record = temp16; } else { @@ -192,9 +202,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static global::Record InitializeRecordAction(IConfiguration configuration, BinderOptions? binderOptions) { int x = (int)(10); - if (configuration["x"] is string value13 && !string.IsNullOrEmpty(value13)) + if (TryGetConfigurationValue(configuration, key: "x", out string? value17)) { - x = ParseInt(value13, configuration.GetSection("x").Path); + if (!string.IsNullOrEmpty(value17)) + { + x = ParseInt(value17, configuration.GetSection("x").Path); + } } return new global::Record(x) @@ -203,6 +216,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration }; } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -270,7 +295,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind.generated.txt index 499cd068953bd9..ddc13d85ae3ecc 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind.generated.txt @@ -87,9 +87,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -98,7 +101,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { instance[section.Key] = value; } @@ -121,7 +124,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value0) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value0)) { instance.MyString = value0; } @@ -134,45 +137,51 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value1 && !string.IsNullOrEmpty(value1)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value1)) { - instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value1)) + { + instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + var value2 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value2) is IConfigurationSection section3) { - global::System.Collections.Generic.List? temp4 = instance.MyList; - temp4 ??= new global::System.Collections.Generic.List(); - BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp4; + global::System.Collections.Generic.List? temp5 = instance.MyList; + temp5 ??= new global::System.Collections.Generic.List(); + BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp5; } else { instance.MyList = instance.MyList; } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + var value6 = configuration.GetSection("MyDictionary"); + if (AsConfigWithChildren(value6) is IConfigurationSection section7) { - global::System.Collections.Generic.Dictionary? temp7 = instance.MyDictionary; - temp7 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp7; + global::System.Collections.Generic.Dictionary? temp9 = instance.MyDictionary; + temp9 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp9; } else { instance.MyDictionary = instance.MyDictionary; } - if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) + var value10 = configuration.GetSection("MyComplexDictionary"); + if (AsConfigWithChildren(value10) is IConfigurationSection section11) { - global::System.Collections.Generic.Dictionary? temp10 = instance.MyComplexDictionary; - temp10 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyComplexDictionary = temp10; + global::System.Collections.Generic.Dictionary? temp13 = instance.MyComplexDictionary; + temp13 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); + instance.MyComplexDictionary = temp13; } else { @@ -180,6 +189,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -238,7 +259,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Instance.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Instance.generated.txt index 43edcd50559144..9a3841f52573c2 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Instance.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Instance.generated.txt @@ -57,9 +57,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -68,7 +71,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { instance[section.Key] = value; } @@ -91,7 +94,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value0) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value0)) { instance.MyString = value0; } @@ -104,45 +107,51 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value1 && !string.IsNullOrEmpty(value1)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value1)) { - instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value1)) + { + instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + var value2 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value2) is IConfigurationSection section3) { - global::System.Collections.Generic.List? temp4 = instance.MyList; - temp4 ??= new global::System.Collections.Generic.List(); - BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp4; + global::System.Collections.Generic.List? temp5 = instance.MyList; + temp5 ??= new global::System.Collections.Generic.List(); + BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp5; } else { instance.MyList = instance.MyList; } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + var value6 = configuration.GetSection("MyDictionary"); + if (AsConfigWithChildren(value6) is IConfigurationSection section7) { - global::System.Collections.Generic.Dictionary? temp7 = instance.MyDictionary; - temp7 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp7; + global::System.Collections.Generic.Dictionary? temp9 = instance.MyDictionary; + temp9 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp9; } else { instance.MyDictionary = instance.MyDictionary; } - if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) + var value10 = configuration.GetSection("MyComplexDictionary"); + if (AsConfigWithChildren(value10) is IConfigurationSection section11) { - global::System.Collections.Generic.Dictionary? temp10 = instance.MyComplexDictionary; - temp10 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyComplexDictionary = temp10; + global::System.Collections.Generic.Dictionary? temp13 = instance.MyComplexDictionary; + temp13 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); + instance.MyComplexDictionary = temp13; } else { @@ -150,6 +159,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -190,7 +211,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Instance_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Instance_BinderOptions.generated.txt index b6e930a7f3e3da..7e77ce6879fb63 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Instance_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Instance_BinderOptions.generated.txt @@ -57,9 +57,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -68,7 +71,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { instance[section.Key] = value; } @@ -91,7 +94,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value0) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value0)) { instance.MyString = value0; } @@ -104,45 +107,51 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value1 && !string.IsNullOrEmpty(value1)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value1)) { - instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value1)) + { + instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + var value2 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value2) is IConfigurationSection section3) { - global::System.Collections.Generic.List? temp4 = instance.MyList; - temp4 ??= new global::System.Collections.Generic.List(); - BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp4; + global::System.Collections.Generic.List? temp5 = instance.MyList; + temp5 ??= new global::System.Collections.Generic.List(); + BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp5; } else { instance.MyList = instance.MyList; } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + var value6 = configuration.GetSection("MyDictionary"); + if (AsConfigWithChildren(value6) is IConfigurationSection section7) { - global::System.Collections.Generic.Dictionary? temp7 = instance.MyDictionary; - temp7 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp7; + global::System.Collections.Generic.Dictionary? temp9 = instance.MyDictionary; + temp9 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp9; } else { instance.MyDictionary = instance.MyDictionary; } - if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) + var value10 = configuration.GetSection("MyComplexDictionary"); + if (AsConfigWithChildren(value10) is IConfigurationSection section11) { - global::System.Collections.Generic.Dictionary? temp10 = instance.MyComplexDictionary; - temp10 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyComplexDictionary = temp10; + global::System.Collections.Generic.Dictionary? temp13 = instance.MyComplexDictionary; + temp13 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); + instance.MyComplexDictionary = temp13; } else { @@ -150,6 +159,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -208,7 +229,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Key_Instance.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Key_Instance.generated.txt index 471dd1aeded6e1..1f405228ea84b0 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Key_Instance.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Key_Instance.generated.txt @@ -57,9 +57,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -68,7 +71,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { instance[section.Key] = value; } @@ -91,7 +94,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value0) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value0)) { instance.MyString = value0; } @@ -104,45 +107,51 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value1 && !string.IsNullOrEmpty(value1)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value1)) { - instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value1)) + { + instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + var value2 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value2) is IConfigurationSection section3) { - global::System.Collections.Generic.List? temp4 = instance.MyList; - temp4 ??= new global::System.Collections.Generic.List(); - BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp4; + global::System.Collections.Generic.List? temp5 = instance.MyList; + temp5 ??= new global::System.Collections.Generic.List(); + BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp5; } else { instance.MyList = instance.MyList; } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + var value6 = configuration.GetSection("MyDictionary"); + if (AsConfigWithChildren(value6) is IConfigurationSection section7) { - global::System.Collections.Generic.Dictionary? temp7 = instance.MyDictionary; - temp7 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp7; + global::System.Collections.Generic.Dictionary? temp9 = instance.MyDictionary; + temp9 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp9; } else { instance.MyDictionary = instance.MyDictionary; } - if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) + var value10 = configuration.GetSection("MyComplexDictionary"); + if (AsConfigWithChildren(value10) is IConfigurationSection section11) { - global::System.Collections.Generic.Dictionary? temp10 = instance.MyComplexDictionary; - temp10 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyComplexDictionary = temp10; + global::System.Collections.Generic.Dictionary? temp13 = instance.MyComplexDictionary; + temp13 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); + instance.MyComplexDictionary = temp13; } else { @@ -150,6 +159,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -190,7 +211,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_ParseTypeFromMethodParam.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_ParseTypeFromMethodParam.generated.txt index 9f8f951bff2980..190b61098f5fd2 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_ParseTypeFromMethodParam.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_ParseTypeFromMethodParam.generated.txt @@ -54,6 +54,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #endregion IConfiguration extensions. #region Core binding extensions. + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + public static BinderOptions? GetBinderOptions(Action? configureOptions) { if (configureOptions is null) diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get.generated.txt index cf7bf6a8fbfe20..0e3bd9deee2ad3 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get.generated.txt @@ -86,9 +86,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -99,9 +102,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - temp2.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + temp2.Add(ParseInt(value, section.Path)); + } } } @@ -114,7 +120,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { instance[section.Key] = value; } @@ -125,7 +131,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value3) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value3)) { instance.MyString = value3; } @@ -138,45 +144,55 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value4 && !string.IsNullOrEmpty(value4)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value4)) { - instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value4)) + { + instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + var value5 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value5) is IConfigurationSection section6) { - global::System.Collections.Generic.List? temp7 = instance.MyList; - temp7 ??= new global::System.Collections.Generic.List(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp7; + global::System.Collections.Generic.List? temp8 = instance.MyList; + temp8 ??= new global::System.Collections.Generic.List(); + BindCore(section6, ref temp8, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp8; } else { instance.MyList = instance.MyList; } - if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section8) + var value9 = configuration.GetSection("MyArray"); + if (AsConfigWithChildren(value9) is IConfigurationSection section10) { - int[]? temp10 = instance.MyArray; - temp10 ??= new int[0]; - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyArray = temp10; + int[]? temp12 = instance.MyArray; + temp12 ??= new int[0]; + BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); + instance.MyArray = temp12; } else { instance.MyArray = instance.MyArray; } + if (instance.MyArray is null && TryGetConfigurationValue(value9, key: null, out string? value13) && value13 == string.Empty) + { + instance.MyArray = global::System.Array.Empty(); + } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + var value14 = configuration.GetSection("MyDictionary"); + if (AsConfigWithChildren(value14) is IConfigurationSection section15) { - global::System.Collections.Generic.Dictionary? temp13 = instance.MyDictionary; - temp13 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp13; + global::System.Collections.Generic.Dictionary? temp17 = instance.MyDictionary; + temp17 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section15, ref temp17, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp17; } else { @@ -188,9 +204,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); - if (configuration["MyInt"] is string value14 && !string.IsNullOrEmpty(value14)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value18)) { - instance.MyInt = ParseInt(value14, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value18)) + { + instance.MyInt = ParseInt(value18, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { @@ -198,6 +217,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -265,7 +296,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue.generated.txt index dcea57b57f3300..5a5be508d1e0ca 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue.generated.txt @@ -59,29 +59,39 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration IConfigurationSection section = configuration.GetSection(key); - if (section.Value is not string value) + if (TryGetConfigurationValue(section, key: null, out string? value) && !string.IsNullOrEmpty(value)) { - return null; + if (type == typeof(int)) + { + return ParseInt(value, section.Path); + } + else if (type == typeof(bool?)) + { + return ParseBool(value, section.Path); + } + else if (type == typeof(byte[])) + { + return ParseByteArray(value, section.Path); + } + else if (type == typeof(global::System.Globalization.CultureInfo)) + { + return ParseSystemGlobalizationCultureInfo(value, section.Path); + } } - if (type == typeof(int)) - { - return ParseInt(value, section.Path); - } - else if (type == typeof(bool?)) - { - return ParseBool(value, section.Path); - } - else if (type == typeof(byte[])) - { - return ParseByteArray(value, section.Path); - } - else if (type == typeof(global::System.Globalization.CultureInfo)) + return null; + } + + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) { - return ParseSystemGlobalizationCultureInfo(value, section.Path); + return section.TryGetValue(key, out value); } - - return null; + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; } public static int ParseInt(string value, string? path) @@ -92,7 +102,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } @@ -104,7 +114,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(bool)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(bool)}'.", exception); } } @@ -116,7 +126,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(byte[])}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(byte[])}'.", exception); } } @@ -128,7 +138,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.Globalization.CultureInfo)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.Globalization.CultureInfo)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue_T_Key.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue_T_Key.generated.txt index 73542f63c19526..048d17a875c909 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue_T_Key.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue_T_Key.generated.txt @@ -45,17 +45,27 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration IConfigurationSection section = configuration.GetSection(key); - if (section.Value is not string value) + if (TryGetConfigurationValue(section, key: null, out string? value) && !string.IsNullOrEmpty(value)) { - return null; + if (type == typeof(int)) + { + return ParseInt(value, section.Path); + } } - if (type == typeof(int)) + return null; + } + + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) { - return ParseInt(value, section.Path); + return section.TryGetValue(key, out value); } - - return null; + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; } public static int ParseInt(string value, string? path) @@ -66,7 +76,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue_T_Key_DefaultValue.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue_T_Key_DefaultValue.generated.txt index 25b3911f5e538f..e4afd9f3b5b285 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue_T_Key_DefaultValue.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue_T_Key_DefaultValue.generated.txt @@ -46,17 +46,27 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration IConfigurationSection section = configuration.GetSection(key); - if (section.Value is not string value) + if (TryGetConfigurationValue(section, key: null, out string? value) && !string.IsNullOrEmpty(value)) { - return null; + if (type == typeof(int)) + { + return ParseInt(value, section.Path); + } } - if (type == typeof(int)) + return null; + } + + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) { - return ParseInt(value, section.Path); + return section.TryGetValue(key, out value); } - - return null; + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; } public static int ParseInt(string value, string? path) @@ -67,7 +77,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue_TypeOf_Key.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue_TypeOf_Key.generated.txt index 23cb1d5fa3f55f..aa906f8b1bce84 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue_TypeOf_Key.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue_TypeOf_Key.generated.txt @@ -45,17 +45,27 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration IConfigurationSection section = configuration.GetSection(key); - if (section.Value is not string value) + if (TryGetConfigurationValue(section, key: null, out string? value) && !string.IsNullOrEmpty(value)) { - return null; + if (type == typeof(bool?)) + { + return ParseBool(value, section.Path); + } } - if (type == typeof(bool?)) + return null; + } + + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) { - return ParseBool(value, section.Path); + return section.TryGetValue(key, out value); } - - return null; + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; } public static bool ParseBool(string value, string? path) @@ -66,7 +76,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(bool)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(bool)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue_TypeOf_Key_DefaultValue.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue_TypeOf_Key_DefaultValue.generated.txt index 992acd8e2a528f..4b902228871eb8 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue_TypeOf_Key_DefaultValue.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/GetValue_TypeOf_Key_DefaultValue.generated.txt @@ -46,17 +46,27 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration IConfigurationSection section = configuration.GetSection(key); - if (section.Value is not string value) + if (TryGetConfigurationValue(section, key: null, out string? value) && !string.IsNullOrEmpty(value)) { - return null; + if (type == typeof(global::System.Globalization.CultureInfo)) + { + return ParseSystemGlobalizationCultureInfo(value, section.Path); + } } - if (type == typeof(global::System.Globalization.CultureInfo)) + return null; + } + + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) { - return ParseSystemGlobalizationCultureInfo(value, section.Path); + return section.TryGetValue(key, out value); } - - return null; + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; } public static global::System.Globalization.CultureInfo ParseSystemGlobalizationCultureInfo(string value, string? path) @@ -67,7 +77,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.Globalization.CultureInfo)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.Globalization.CultureInfo)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_PrimitivesOnly.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_PrimitivesOnly.generated.txt index 9b1bc5e1ea5c75..5dab04cdc700f8 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_PrimitivesOnly.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_PrimitivesOnly.generated.txt @@ -68,9 +68,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { throw new InvalidOperationException(); } - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(configuration, key: null, out string? value)) { - return ParseInt(value, section.Path); + if (!string.IsNullOrEmpty(value)) + { + return ParseInt(value, section.Path); + } } } else if (type == typeof(string)) @@ -79,7 +82,10 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { throw new InvalidOperationException(); } - return section.Value; + if (TryGetConfigurationValue(configuration, key: null, out string? value)) + { + return value; + } } else if (type == typeof(float)) { @@ -87,9 +93,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { throw new InvalidOperationException(); } - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(configuration, key: null, out string? value)) { - return ParseFloat(value, section.Path); + if (!string.IsNullOrEmpty(value)) + { + return ParseFloat(value, section.Path); + } } } else if (type == typeof(double)) @@ -98,15 +107,30 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { throw new InvalidOperationException(); } - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(configuration, key: null, out string? value)) { - return ParseDouble(value, section.Path); + if (!string.IsNullOrEmpty(value)) + { + return ParseDouble(value, section.Path); + } } } throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + public static bool HasValueOrChildren(IConfiguration configuration) { if ((configuration as IConfigurationSection)?.Value is not null) @@ -151,7 +175,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } @@ -163,7 +187,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(float)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(float)}'.", exception); } } @@ -175,7 +199,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(double)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(double)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_T.generated.txt index a1fa448fd76f2b..c42dfd5f0ff18a 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_T.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_T.generated.txt @@ -67,9 +67,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -80,9 +83,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - temp1.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + temp1.Add(ParseInt(value, section.Path)); + } } } @@ -95,7 +101,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { instance[section.Key] = value; } @@ -106,7 +112,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value2) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value2)) { instance.MyString = value2; } @@ -119,45 +125,55 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value3 && !string.IsNullOrEmpty(value3)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value3)) { - instance.MyInt = ParseInt(value3, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value3)) + { + instance.MyInt = ParseInt(value3, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section4) + var value4 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value4) is IConfigurationSection section5) { - global::System.Collections.Generic.List? temp6 = instance.MyList; - temp6 ??= new global::System.Collections.Generic.List(); - BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp6; + global::System.Collections.Generic.List? temp7 = instance.MyList; + temp7 ??= new global::System.Collections.Generic.List(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp7; } else { instance.MyList = instance.MyList; } - if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section7) + var value8 = configuration.GetSection("MyArray"); + if (AsConfigWithChildren(value8) is IConfigurationSection section9) { - int[]? temp9 = instance.MyArray; - temp9 ??= new int[0]; - BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); - instance.MyArray = temp9; + int[]? temp11 = instance.MyArray; + temp11 ??= new int[0]; + BindCore(section9, ref temp11, defaultValueIfNotFound: false, binderOptions); + instance.MyArray = temp11; } else { instance.MyArray = instance.MyArray; } + if (instance.MyArray is null && TryGetConfigurationValue(value8, key: null, out string? value12) && value12 == string.Empty) + { + instance.MyArray = global::System.Array.Empty(); + } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section10) + var value13 = configuration.GetSection("MyDictionary"); + if (AsConfigWithChildren(value13) is IConfigurationSection section14) { - global::System.Collections.Generic.Dictionary? temp12 = instance.MyDictionary; - temp12 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp12; + global::System.Collections.Generic.Dictionary? temp16 = instance.MyDictionary; + temp16 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section14, ref temp16, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp16; } else { @@ -165,6 +181,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -232,7 +260,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_T_BinderOptions.generated.txt index 8224f0967d14b9..e7453ef656dae0 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_T_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_T_BinderOptions.generated.txt @@ -67,9 +67,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -80,9 +83,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - temp1.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + temp1.Add(ParseInt(value, section.Path)); + } } } @@ -95,7 +101,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { instance[section.Key] = value; } @@ -106,7 +112,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value2) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value2)) { instance.MyString = value2; } @@ -119,45 +125,55 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value3 && !string.IsNullOrEmpty(value3)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value3)) { - instance.MyInt = ParseInt(value3, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value3)) + { + instance.MyInt = ParseInt(value3, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section4) + var value4 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value4) is IConfigurationSection section5) { - global::System.Collections.Generic.List? temp6 = instance.MyList; - temp6 ??= new global::System.Collections.Generic.List(); - BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp6; + global::System.Collections.Generic.List? temp7 = instance.MyList; + temp7 ??= new global::System.Collections.Generic.List(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp7; } else { instance.MyList = instance.MyList; } - if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section7) + var value8 = configuration.GetSection("MyArray"); + if (AsConfigWithChildren(value8) is IConfigurationSection section9) { - int[]? temp9 = instance.MyArray; - temp9 ??= new int[0]; - BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); - instance.MyArray = temp9; + int[]? temp11 = instance.MyArray; + temp11 ??= new int[0]; + BindCore(section9, ref temp11, defaultValueIfNotFound: false, binderOptions); + instance.MyArray = temp11; } else { instance.MyArray = instance.MyArray; } + if (instance.MyArray is null && TryGetConfigurationValue(value8, key: null, out string? value12) && value12 == string.Empty) + { + instance.MyArray = global::System.Array.Empty(); + } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section10) + var value13 = configuration.GetSection("MyDictionary"); + if (AsConfigWithChildren(value13) is IConfigurationSection section14) { - global::System.Collections.Generic.Dictionary? temp12 = instance.MyDictionary; - temp12 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp12; + global::System.Collections.Generic.Dictionary? temp16 = instance.MyDictionary; + temp16 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section14, ref temp16, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp16; } else { @@ -165,6 +181,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -232,7 +260,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_TypeOf.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_TypeOf.generated.txt index 4ed9fffb984230..a5754a9e133533 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_TypeOf.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_TypeOf.generated.txt @@ -67,9 +67,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); - if (configuration["MyInt"] is string value1 && !string.IsNullOrEmpty(value1)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value1)) { - instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value1)) + { + instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { @@ -77,6 +80,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -144,7 +159,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_TypeOf_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_TypeOf_BinderOptions.generated.txt index 5a576795bfdb9a..d42c36e79fe444 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_TypeOf_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_TypeOf_BinderOptions.generated.txt @@ -67,9 +67,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); - if (configuration["MyInt"] is string value1 && !string.IsNullOrEmpty(value1)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value1)) { - instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value1)) + { + instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { @@ -77,6 +80,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -144,7 +159,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/BindConfiguration.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/BindConfiguration.generated.txt index 70ca8bf3f05462..b0649525453977 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/BindConfiguration.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/BindConfiguration.generated.txt @@ -92,9 +92,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -103,7 +106,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value1) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value1)) { instance.MyString = value1; } @@ -116,21 +119,25 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value2 && !string.IsNullOrEmpty(value2)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value2)) { - instance.MyInt = ParseInt(value2, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value2)) + { + instance.MyInt = ParseInt(value2, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) + var value3 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value3) is IConfigurationSection section4) { - global::System.Collections.Generic.List? temp5 = instance.MyList; - temp5 ??= new global::System.Collections.Generic.List(); - BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp5; + global::System.Collections.Generic.List? temp6 = instance.MyList; + temp6 ??= new global::System.Collections.Generic.List(); + BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp6; } else { @@ -138,6 +145,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -205,7 +224,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/BindConfigurationWithConfigureActions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/BindConfigurationWithConfigureActions.generated.txt index 52d92bd33dffea..2c993d0137a99a 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/BindConfigurationWithConfigureActions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/BindConfigurationWithConfigureActions.generated.txt @@ -92,9 +92,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -103,7 +106,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value1) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value1)) { instance.MyString = value1; } @@ -116,21 +119,25 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value2 && !string.IsNullOrEmpty(value2)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value2)) { - instance.MyInt = ParseInt(value2, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value2)) + { + instance.MyInt = ParseInt(value2, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) + var value3 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value3) is IConfigurationSection section4) { - global::System.Collections.Generic.List? temp5 = instance.MyList; - temp5 ??= new global::System.Collections.Generic.List(); - BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp5; + global::System.Collections.Generic.List? temp6 = instance.MyList; + temp6 ??= new global::System.Collections.Generic.List(); + BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp6; } else { @@ -138,6 +145,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -205,7 +224,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/Bind_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/Bind_T.generated.txt index 8479f9c6672d50..ca1141415b1352 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/Bind_T.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/Bind_T.generated.txt @@ -98,9 +98,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -109,7 +112,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value1) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value1)) { instance.MyString = value1; } @@ -122,21 +125,25 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value2 && !string.IsNullOrEmpty(value2)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value2)) { - instance.MyInt = ParseInt(value2, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value2)) + { + instance.MyInt = ParseInt(value2, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) + var value3 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value3) is IConfigurationSection section4) { - global::System.Collections.Generic.List? temp5 = instance.MyList; - temp5 ??= new global::System.Collections.Generic.List(); - BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp5; + global::System.Collections.Generic.List? temp6 = instance.MyList; + temp6 ??= new global::System.Collections.Generic.List(); + BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp6; } else { @@ -144,6 +151,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -211,7 +230,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/Bind_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/Bind_T_BinderOptions.generated.txt index 6b7825d2ca8ab5..00d3a1dab08a4f 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/Bind_T_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/Bind_T_BinderOptions.generated.txt @@ -92,9 +92,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -103,7 +106,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value1) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value1)) { instance.MyString = value1; } @@ -116,21 +119,25 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value2 && !string.IsNullOrEmpty(value2)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value2)) { - instance.MyInt = ParseInt(value2, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value2)) + { + instance.MyInt = ParseInt(value2, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) + var value3 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value3) is IConfigurationSection section4) { - global::System.Collections.Generic.List? temp5 = instance.MyList; - temp5 ??= new global::System.Collections.Generic.List(); - BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp5; + global::System.Collections.Generic.List? temp6 = instance.MyList; + temp6 ??= new global::System.Collections.Generic.List(); + BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp6; } else { @@ -138,6 +145,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -205,7 +224,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T.generated.txt index 2c322e75c16fef..df203e36ff281b 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T.generated.txt @@ -89,9 +89,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -100,9 +103,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); - if (configuration["MyInt"] is string value1 && !string.IsNullOrEmpty(value1)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value1)) { - instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value1)) + { + instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { @@ -128,7 +134,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { instance[section.Key] = value; } @@ -139,7 +145,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value3) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value3)) { instance.MyString = value3; } @@ -152,45 +158,51 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value4 && !string.IsNullOrEmpty(value4)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value4)) { - instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value4)) + { + instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + var value5 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value5) is IConfigurationSection section6) { - global::System.Collections.Generic.List? temp7 = instance.MyList; - temp7 ??= new global::System.Collections.Generic.List(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp7; + global::System.Collections.Generic.List? temp8 = instance.MyList; + temp8 ??= new global::System.Collections.Generic.List(); + BindCore(section6, ref temp8, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp8; } else { instance.MyList = instance.MyList; } - if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) + var value9 = configuration.GetSection("MyList2"); + if (AsConfigWithChildren(value9) is IConfigurationSection section10) { - global::System.Collections.Generic.List? temp10 = instance.MyList2; - temp10 ??= new global::System.Collections.Generic.List(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyList2 = temp10; + global::System.Collections.Generic.List? temp12 = instance.MyList2; + temp12 ??= new global::System.Collections.Generic.List(); + BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp12; } else { instance.MyList2 = instance.MyList2; } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + var value13 = configuration.GetSection("MyDictionary"); + if (AsConfigWithChildren(value13) is IConfigurationSection section14) { - global::System.Collections.Generic.Dictionary? temp13 = instance.MyDictionary; - temp13 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp13; + global::System.Collections.Generic.Dictionary? temp16 = instance.MyDictionary; + temp16 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section14, ref temp16, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp16; } else { @@ -198,6 +210,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -265,7 +289,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_BinderOptions.generated.txt index d9d27cdee4c778..bd49bd223708bd 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_BinderOptions.generated.txt @@ -89,9 +89,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -100,9 +103,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); - if (configuration["MyInt"] is string value1 && !string.IsNullOrEmpty(value1)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value1)) { - instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value1)) + { + instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { @@ -128,7 +134,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { instance[section.Key] = value; } @@ -139,7 +145,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value3) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value3)) { instance.MyString = value3; } @@ -152,45 +158,51 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value4 && !string.IsNullOrEmpty(value4)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value4)) { - instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value4)) + { + instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + var value5 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value5) is IConfigurationSection section6) { - global::System.Collections.Generic.List? temp7 = instance.MyList; - temp7 ??= new global::System.Collections.Generic.List(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp7; + global::System.Collections.Generic.List? temp8 = instance.MyList; + temp8 ??= new global::System.Collections.Generic.List(); + BindCore(section6, ref temp8, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp8; } else { instance.MyList = instance.MyList; } - if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) + var value9 = configuration.GetSection("MyList2"); + if (AsConfigWithChildren(value9) is IConfigurationSection section10) { - global::System.Collections.Generic.List? temp10 = instance.MyList2; - temp10 ??= new global::System.Collections.Generic.List(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyList2 = temp10; + global::System.Collections.Generic.List? temp12 = instance.MyList2; + temp12 ??= new global::System.Collections.Generic.List(); + BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp12; } else { instance.MyList2 = instance.MyList2; } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + var value13 = configuration.GetSection("MyDictionary"); + if (AsConfigWithChildren(value13) is IConfigurationSection section14) { - global::System.Collections.Generic.Dictionary? temp13 = instance.MyDictionary; - temp13 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp13; + global::System.Collections.Generic.Dictionary? temp16 = instance.MyDictionary; + temp16 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section14, ref temp16, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp16; } else { @@ -198,6 +210,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -265,7 +289,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_name.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_name.generated.txt index 68019fb6069b3a..adbbe382f70d7e 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_name.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_name.generated.txt @@ -89,9 +89,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -100,9 +103,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); - if (configuration["MyInt"] is string value1 && !string.IsNullOrEmpty(value1)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value1)) { - instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value1)) + { + instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { @@ -128,7 +134,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { instance[section.Key] = value; } @@ -139,7 +145,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value3) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value3)) { instance.MyString = value3; } @@ -152,45 +158,51 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value4 && !string.IsNullOrEmpty(value4)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value4)) { - instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value4)) + { + instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + var value5 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value5) is IConfigurationSection section6) { - global::System.Collections.Generic.List? temp7 = instance.MyList; - temp7 ??= new global::System.Collections.Generic.List(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp7; + global::System.Collections.Generic.List? temp8 = instance.MyList; + temp8 ??= new global::System.Collections.Generic.List(); + BindCore(section6, ref temp8, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp8; } else { instance.MyList = instance.MyList; } - if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) + var value9 = configuration.GetSection("MyList2"); + if (AsConfigWithChildren(value9) is IConfigurationSection section10) { - global::System.Collections.Generic.List? temp10 = instance.MyList2; - temp10 ??= new global::System.Collections.Generic.List(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyList2 = temp10; + global::System.Collections.Generic.List? temp12 = instance.MyList2; + temp12 ??= new global::System.Collections.Generic.List(); + BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp12; } else { instance.MyList2 = instance.MyList2; } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + var value13 = configuration.GetSection("MyDictionary"); + if (AsConfigWithChildren(value13) is IConfigurationSection section14) { - global::System.Collections.Generic.Dictionary? temp13 = instance.MyDictionary; - temp13 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp13; + global::System.Collections.Generic.Dictionary? temp16 = instance.MyDictionary; + temp16 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section14, ref temp16, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp16; } else { @@ -198,6 +210,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -265,7 +289,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_name_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_name_BinderOptions.generated.txt index 80ad0e0943f111..07bb3e3a6651bb 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_name_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_name_BinderOptions.generated.txt @@ -83,9 +83,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + instance.Add(ParseInt(value, section.Path)); + } } } } @@ -94,9 +97,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); - if (configuration["MyInt"] is string value1 && !string.IsNullOrEmpty(value1)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value1)) { - instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value1)) + { + instance.MyInt = ParseInt(value1, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { @@ -122,7 +128,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { instance[section.Key] = value; } @@ -133,7 +139,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value3) + if (TryGetConfigurationValue(configuration, key: "MyString", out string? value3)) { instance.MyString = value3; } @@ -146,45 +152,51 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["MyInt"] is string value4 && !string.IsNullOrEmpty(value4)) + if (TryGetConfigurationValue(configuration, key: "MyInt", out string? value4)) { - instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + if (!string.IsNullOrEmpty(value4)) + { + instance.MyInt = ParseInt(value4, configuration.GetSection("MyInt").Path); + } } else if (defaultValueIfNotFound) { instance.MyInt = instance.MyInt; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + var value5 = configuration.GetSection("MyList"); + if (AsConfigWithChildren(value5) is IConfigurationSection section6) { - global::System.Collections.Generic.List? temp7 = instance.MyList; - temp7 ??= new global::System.Collections.Generic.List(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp7; + global::System.Collections.Generic.List? temp8 = instance.MyList; + temp8 ??= new global::System.Collections.Generic.List(); + BindCore(section6, ref temp8, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp8; } else { instance.MyList = instance.MyList; } - if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) + var value9 = configuration.GetSection("MyList2"); + if (AsConfigWithChildren(value9) is IConfigurationSection section10) { - global::System.Collections.Generic.List? temp10 = instance.MyList2; - temp10 ??= new global::System.Collections.Generic.List(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyList2 = temp10; + global::System.Collections.Generic.List? temp12 = instance.MyList2; + temp12 ??= new global::System.Collections.Generic.List(); + BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp12; } else { instance.MyList2 = instance.MyList2; } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + var value13 = configuration.GetSection("MyDictionary"); + if (AsConfigWithChildren(value13) is IConfigurationSection section14) { - global::System.Collections.Generic.Dictionary? temp13 = instance.MyDictionary; - temp13 ??= new global::System.Collections.Generic.Dictionary(); - BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp13; + global::System.Collections.Generic.Dictionary? temp16 = instance.MyDictionary; + temp16 ??= new global::System.Collections.Generic.Dictionary(); + BindCore(section14, ref temp16, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp16; } else { @@ -192,6 +204,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -259,7 +283,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/Collections.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/Collections.generated.txt index 2d0e1f3207ef27..14dfa0dba98849 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/Collections.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/Collections.generated.txt @@ -68,9 +68,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - instance[section.Key] = ParseInt(value, section.Path); + if (!string.IsNullOrEmpty(value)) + { + instance[section.Key] = ParseInt(value, section.Path); + } } } } @@ -79,7 +82,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { instance.Add(value); } @@ -95,9 +98,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - temp.Add(ParseInt(value, section.Path)); + if (!string.IsNullOrEmpty(value)) + { + temp.Add(ParseInt(value, section.Path)); + } } } } @@ -111,9 +117,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value && !string.IsNullOrEmpty(value)) + if (TryGetConfigurationValue(section, key: null, out string? value)) { - temp[section.Key] = ParseInt(value, section.Path); + if (!string.IsNullOrEmpty(value)) + { + temp[section.Key] = ParseInt(value, section.Path); + } } } } @@ -124,7 +133,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { temp.Add(value); } @@ -135,61 +144,66 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClassWithCustomCollections), s_configKeys_ProgramMyClassWithCustomCollections, configuration, binderOptions); - if (AsConfigWithChildren(configuration.GetSection("CustomDictionary")) is IConfigurationSection section1) + var value1 = configuration.GetSection("CustomDictionary"); + if (AsConfigWithChildren(value1) is IConfigurationSection section2) { - global::Program.CustomDictionary? temp3 = instance.CustomDictionary; - temp3 ??= new global::Program.CustomDictionary(); - BindCore(section1, ref temp3, defaultValueIfNotFound: false, binderOptions); - instance.CustomDictionary = temp3; + global::Program.CustomDictionary? temp4 = instance.CustomDictionary; + temp4 ??= new global::Program.CustomDictionary(); + BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); + instance.CustomDictionary = temp4; } else { instance.CustomDictionary = instance.CustomDictionary; } - if (AsConfigWithChildren(configuration.GetSection("CustomList")) is IConfigurationSection section4) + var value5 = configuration.GetSection("CustomList"); + if (AsConfigWithChildren(value5) is IConfigurationSection section6) { - global::Program.CustomList? temp6 = instance.CustomList; - temp6 ??= new global::Program.CustomList(); - BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); - instance.CustomList = temp6; + global::Program.CustomList? temp8 = instance.CustomList; + temp8 ??= new global::Program.CustomList(); + BindCore(section6, ref temp8, defaultValueIfNotFound: false, binderOptions); + instance.CustomList = temp8; } else { instance.CustomList = instance.CustomList; } - if (AsConfigWithChildren(configuration.GetSection("IReadOnlyList")) is IConfigurationSection section7) + var value9 = configuration.GetSection("IReadOnlyList"); + if (AsConfigWithChildren(value9) is IConfigurationSection section10) { - global::System.Collections.Generic.IReadOnlyList? temp9 = instance.IReadOnlyList; - temp9 = temp9 is null ? (global::System.Collections.Generic.IReadOnlyList)new List() : (global::System.Collections.Generic.IReadOnlyList)new List(temp9); - BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); - instance.IReadOnlyList = temp9; + global::System.Collections.Generic.IReadOnlyList? temp12 = instance.IReadOnlyList; + temp12 = temp12 is null ? (global::System.Collections.Generic.IReadOnlyList)new List() : (global::System.Collections.Generic.IReadOnlyList)new List(temp12); + BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); + instance.IReadOnlyList = temp12; } else { instance.IReadOnlyList = instance.IReadOnlyList; } - if (AsConfigWithChildren(configuration.GetSection("IReadOnlyDictionary")) is IConfigurationSection section10) + var value13 = configuration.GetSection("IReadOnlyDictionary"); + if (AsConfigWithChildren(value13) is IConfigurationSection section14) { - global::System.Collections.Generic.IReadOnlyDictionary? temp12 = instance.IReadOnlyDictionary; - temp12 = temp12 is null ? (global::System.Collections.Generic.IReadOnlyDictionary)new Dictionary() : (global::System.Collections.Generic.IReadOnlyDictionary)temp12.ToDictionary(pair => pair.Key, pair => pair.Value); - BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); - instance.IReadOnlyDictionary = temp12; + global::System.Collections.Generic.IReadOnlyDictionary? temp16 = instance.IReadOnlyDictionary; + temp16 = temp16 is null ? (global::System.Collections.Generic.IReadOnlyDictionary)new Dictionary() : (global::System.Collections.Generic.IReadOnlyDictionary)temp16.ToDictionary(pair => pair.Key, pair => pair.Value); + BindCore(section14, ref temp16, defaultValueIfNotFound: false, binderOptions); + instance.IReadOnlyDictionary = temp16; } else { instance.IReadOnlyDictionary = instance.IReadOnlyDictionary; } - if (AsConfigWithChildren(configuration.GetSection("CollectionStructExplicit")) is IConfigurationSection section13) + var value17 = configuration.GetSection("CollectionStructExplicit"); + if (AsConfigWithChildren(value17) is IConfigurationSection section18) { - global::Program.CollectionStructExplicit temp14 = instance.CollectionStructExplicit; - var temp15 = new global::Program.CollectionStructExplicit(); - BindCore(section13, ref temp15, defaultValueIfNotFound: false, binderOptions); - instance.CollectionStructExplicit = temp15; - temp14 = temp15; + global::Program.CollectionStructExplicit temp19 = instance.CollectionStructExplicit; + var temp20 = new global::Program.CollectionStructExplicit(); + BindCore(section18, ref temp20, defaultValueIfNotFound: false, binderOptions); + instance.CollectionStructExplicit = temp20; + temp19 = temp20; } else { @@ -197,6 +211,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -264,7 +290,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/DefaultConstructorParameters.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/DefaultConstructorParameters.generated.txt index f726e2254264bf..4715e0584ca320 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/DefaultConstructorParameters.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/DefaultConstructorParameters.generated.txt @@ -61,92 +61,182 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static global::Program.ClassWhereParametersHaveDefaultValue InitializeProgramClassWhereParametersHaveDefaultValue(IConfiguration configuration, BinderOptions? binderOptions) { string name = "John Doe"!; - if (configuration["Name"] is string value0) + if (TryGetConfigurationValue(configuration, key: "Name", out string? value0)) { name = value0; } string address = "1 Microsoft Way"!; - if (configuration["Address"] is string value1) + if (TryGetConfigurationValue(configuration, key: "Address", out string? value1)) { address = value1; } int age = (int)(42); - if (configuration["Age"] is string value2 && !string.IsNullOrEmpty(value2)) + if (TryGetConfigurationValue(configuration, key: "Age", out string? value2)) { - age = ParseInt(value2, configuration.GetSection("Age").Path); + if (!string.IsNullOrEmpty(value2)) + { + age = ParseInt(value2, configuration.GetSection("Age").Path); + } } float f = 42F; - if (configuration["F"] is string value3 && !string.IsNullOrEmpty(value3)) + if (TryGetConfigurationValue(configuration, key: "F", out string? value3)) { - f = ParseFloat(value3, configuration.GetSection("F").Path); + if (!string.IsNullOrEmpty(value3)) + { + f = ParseFloat(value3, configuration.GetSection("F").Path); + } } double d = 3.1415899999999999D; - if (configuration["D"] is string value4 && !string.IsNullOrEmpty(value4)) + if (TryGetConfigurationValue(configuration, key: "D", out string? value4)) { - d = ParseDouble(value4, configuration.GetSection("D").Path); + if (!string.IsNullOrEmpty(value4)) + { + d = ParseDouble(value4, configuration.GetSection("D").Path); + } } decimal m = 3.1415926535897932384626433M; - if (configuration["M"] is string value5 && !string.IsNullOrEmpty(value5)) + if (TryGetConfigurationValue(configuration, key: "M", out string? value5)) { - m = ParseDecimal(value5, configuration.GetSection("M").Path); + if (!string.IsNullOrEmpty(value5)) + { + m = ParseDecimal(value5, configuration.GetSection("M").Path); + } } global::System.StringComparison sc = (global::System.StringComparison)(4); - if (configuration["SC"] is string value6 && !string.IsNullOrEmpty(value6)) + if (TryGetConfigurationValue(configuration, key: "SC", out string? value6)) { - sc = ParseEnum(value6, configuration.GetSection("SC").Path); + if (!string.IsNullOrEmpty(value6)) + { + sc = ParseEnum(value6, configuration.GetSection("SC").Path); + } } char c = 'q'; - if (configuration["C"] is string value7 && !string.IsNullOrEmpty(value7)) + if (TryGetConfigurationValue(configuration, key: "C", out string? value7)) { - c = ParseChar(value7, configuration.GetSection("C").Path); + if (!string.IsNullOrEmpty(value7)) + { + c = ParseChar(value7, configuration.GetSection("C").Path); + } } int? nage = (int?)(42); - if (configuration["NAge"] is string value8 && !string.IsNullOrEmpty(value8)) + if (TryGetConfigurationValue(configuration, key: "NAge", out string? value8)) { - nage = ParseInt(value8, configuration.GetSection("NAge").Path); + if (value8 is null) + { + nage = null; + } + else + { + if (!string.IsNullOrEmpty(value8)) + { + nage = ParseInt(value8, configuration.GetSection("NAge").Path); + } + } } float? nf = 42F; - if (configuration["NF"] is string value9 && !string.IsNullOrEmpty(value9)) + if (TryGetConfigurationValue(configuration, key: "NF", out string? value9)) { - nf = ParseFloat(value9, configuration.GetSection("NF").Path); + if (value9 is null) + { + nf = null; + } + else + { + if (!string.IsNullOrEmpty(value9)) + { + nf = ParseFloat(value9, configuration.GetSection("NF").Path); + } + } } double? nd = 3.1415899999999999D; - if (configuration["ND"] is string value10 && !string.IsNullOrEmpty(value10)) + if (TryGetConfigurationValue(configuration, key: "ND", out string? value10)) { - nd = ParseDouble(value10, configuration.GetSection("ND").Path); + if (value10 is null) + { + nd = null; + } + else + { + if (!string.IsNullOrEmpty(value10)) + { + nd = ParseDouble(value10, configuration.GetSection("ND").Path); + } + } } decimal? nm = 3.1415926535897932384626433M; - if (configuration["NM"] is string value11 && !string.IsNullOrEmpty(value11)) + if (TryGetConfigurationValue(configuration, key: "NM", out string? value11)) { - nm = ParseDecimal(value11, configuration.GetSection("NM").Path); + if (value11 is null) + { + nm = null; + } + else + { + if (!string.IsNullOrEmpty(value11)) + { + nm = ParseDecimal(value11, configuration.GetSection("NM").Path); + } + } } global::System.StringComparison? nsc = (global::System.StringComparison?)(4); - if (configuration["NSC"] is string value12 && !string.IsNullOrEmpty(value12)) + if (TryGetConfigurationValue(configuration, key: "NSC", out string? value12)) { - nsc = ParseEnum(value12, configuration.GetSection("NSC").Path); + if (value12 is null) + { + nsc = null; + } + else + { + if (!string.IsNullOrEmpty(value12)) + { + nsc = ParseEnum(value12, configuration.GetSection("NSC").Path); + } + } } char? nc = 'q'; - if (configuration["NC"] is string value13 && !string.IsNullOrEmpty(value13)) + if (TryGetConfigurationValue(configuration, key: "NC", out string? value13)) { - nc = ParseChar(value13, configuration.GetSection("NC").Path); + if (value13 is null) + { + nc = null; + } + else + { + if (!string.IsNullOrEmpty(value13)) + { + nc = ParseChar(value13, configuration.GetSection("NC").Path); + } + } } return new global::Program.ClassWhereParametersHaveDefaultValue(name, address, age, f, d, m, sc, c, nage, nf, nd, nm, nsc, nc); } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -178,7 +268,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(T)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(T)}'.", exception); } } @@ -190,7 +280,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } @@ -202,7 +292,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(float)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(float)}'.", exception); } } @@ -214,7 +304,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(double)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(double)}'.", exception); } } @@ -226,7 +316,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(decimal)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(decimal)}'.", exception); } } @@ -238,7 +328,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(char)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(char)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/EmptyConfigType.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/EmptyConfigType.generated.txt index 82b035f6dbf634..03084d47bd9c25 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/EmptyConfigType.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/EmptyConfigType.generated.txt @@ -64,12 +64,25 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::TypeWithNoMembers_Wrapper), s_configKeys_TypeWithNoMembers_Wrapper, configuration, binderOptions); - if (AsConfigWithChildren(configuration.GetSection("Member")) is IConfigurationSection section0) + var value0 = configuration.GetSection("Member"); + if (AsConfigWithChildren(value0) is IConfigurationSection section1) { instance.Member ??= new global::TypeWithNoMembers(); } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/Primitives.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/Primitives.generated.txt index 92787bd1909dfc..b252dd3f162698 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/Primitives.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/Primitives.generated.txt @@ -57,52 +57,67 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["Prop0"] is string value0 && !string.IsNullOrEmpty(value0)) + if (TryGetConfigurationValue(configuration, key: "Prop0", out string? value0)) { - instance.Prop0 = ParseBool(value0, configuration.GetSection("Prop0").Path); + if (!string.IsNullOrEmpty(value0)) + { + instance.Prop0 = ParseBool(value0, configuration.GetSection("Prop0").Path); + } } else if (defaultValueIfNotFound) { instance.Prop0 = instance.Prop0; } - if (configuration["Prop1"] is string value1 && !string.IsNullOrEmpty(value1)) + if (TryGetConfigurationValue(configuration, key: "Prop1", out string? value1)) { - instance.Prop1 = ParseByte(value1, configuration.GetSection("Prop1").Path); + if (!string.IsNullOrEmpty(value1)) + { + instance.Prop1 = ParseByte(value1, configuration.GetSection("Prop1").Path); + } } else if (defaultValueIfNotFound) { instance.Prop1 = instance.Prop1; } - if (configuration["Prop2"] is string value2 && !string.IsNullOrEmpty(value2)) + if (TryGetConfigurationValue(configuration, key: "Prop2", out string? value2)) { - instance.Prop2 = ParseSbyte(value2, configuration.GetSection("Prop2").Path); + if (!string.IsNullOrEmpty(value2)) + { + instance.Prop2 = ParseSbyte(value2, configuration.GetSection("Prop2").Path); + } } else if (defaultValueIfNotFound) { instance.Prop2 = instance.Prop2; } - if (configuration["Prop3"] is string value3 && !string.IsNullOrEmpty(value3)) + if (TryGetConfigurationValue(configuration, key: "Prop3", out string? value3)) { - instance.Prop3 = ParseChar(value3, configuration.GetSection("Prop3").Path); + if (!string.IsNullOrEmpty(value3)) + { + instance.Prop3 = ParseChar(value3, configuration.GetSection("Prop3").Path); + } } else if (defaultValueIfNotFound) { instance.Prop3 = instance.Prop3; } - if (configuration["Prop4"] is string value4 && !string.IsNullOrEmpty(value4)) + if (TryGetConfigurationValue(configuration, key: "Prop4", out string? value4)) { - instance.Prop4 = ParseDouble(value4, configuration.GetSection("Prop4").Path); + if (!string.IsNullOrEmpty(value4)) + { + instance.Prop4 = ParseDouble(value4, configuration.GetSection("Prop4").Path); + } } else if (defaultValueIfNotFound) { instance.Prop4 = instance.Prop4; } - if (configuration["Prop5"] is string value5) + if (TryGetConfigurationValue(configuration, key: "Prop5", out string? value5)) { instance.Prop5 = value5; } @@ -115,70 +130,91 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["Prop6"] is string value6 && !string.IsNullOrEmpty(value6)) + if (TryGetConfigurationValue(configuration, key: "Prop6", out string? value6)) { - instance.Prop6 = ParseInt(value6, configuration.GetSection("Prop6").Path); + if (!string.IsNullOrEmpty(value6)) + { + instance.Prop6 = ParseInt(value6, configuration.GetSection("Prop6").Path); + } } else if (defaultValueIfNotFound) { instance.Prop6 = instance.Prop6; } - if (configuration["Prop8"] is string value7 && !string.IsNullOrEmpty(value7)) + if (TryGetConfigurationValue(configuration, key: "Prop8", out string? value7)) { - instance.Prop8 = ParseShort(value7, configuration.GetSection("Prop8").Path); + if (!string.IsNullOrEmpty(value7)) + { + instance.Prop8 = ParseShort(value7, configuration.GetSection("Prop8").Path); + } } else if (defaultValueIfNotFound) { instance.Prop8 = instance.Prop8; } - if (configuration["Prop9"] is string value8 && !string.IsNullOrEmpty(value8)) + if (TryGetConfigurationValue(configuration, key: "Prop9", out string? value8)) { - instance.Prop9 = ParseLong(value8, configuration.GetSection("Prop9").Path); + if (!string.IsNullOrEmpty(value8)) + { + instance.Prop9 = ParseLong(value8, configuration.GetSection("Prop9").Path); + } } else if (defaultValueIfNotFound) { instance.Prop9 = instance.Prop9; } - if (configuration["Prop10"] is string value9 && !string.IsNullOrEmpty(value9)) + if (TryGetConfigurationValue(configuration, key: "Prop10", out string? value9)) { - instance.Prop10 = ParseFloat(value9, configuration.GetSection("Prop10").Path); + if (!string.IsNullOrEmpty(value9)) + { + instance.Prop10 = ParseFloat(value9, configuration.GetSection("Prop10").Path); + } } else if (defaultValueIfNotFound) { instance.Prop10 = instance.Prop10; } - if (configuration["Prop13"] is string value10 && !string.IsNullOrEmpty(value10)) + if (TryGetConfigurationValue(configuration, key: "Prop13", out string? value10)) { - instance.Prop13 = ParseUshort(value10, configuration.GetSection("Prop13").Path); + if (!string.IsNullOrEmpty(value10)) + { + instance.Prop13 = ParseUshort(value10, configuration.GetSection("Prop13").Path); + } } else if (defaultValueIfNotFound) { instance.Prop13 = instance.Prop13; } - if (configuration["Prop14"] is string value11 && !string.IsNullOrEmpty(value11)) + if (TryGetConfigurationValue(configuration, key: "Prop14", out string? value11)) { - instance.Prop14 = ParseUint(value11, configuration.GetSection("Prop14").Path); + if (!string.IsNullOrEmpty(value11)) + { + instance.Prop14 = ParseUint(value11, configuration.GetSection("Prop14").Path); + } } else if (defaultValueIfNotFound) { instance.Prop14 = instance.Prop14; } - if (configuration["Prop15"] is string value12 && !string.IsNullOrEmpty(value12)) + if (TryGetConfigurationValue(configuration, key: "Prop15", out string? value12)) { - instance.Prop15 = ParseUlong(value12, configuration.GetSection("Prop15").Path); + if (!string.IsNullOrEmpty(value12)) + { + instance.Prop15 = ParseUlong(value12, configuration.GetSection("Prop15").Path); + } } else if (defaultValueIfNotFound) { instance.Prop15 = instance.Prop15; } - if (configuration["Prop16"] is string value13) + if (TryGetConfigurationValue(configuration, key: "Prop16", out string? value13)) { instance.Prop16 = value13; } @@ -191,9 +227,19 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["Prop17"] is string value14 && !string.IsNullOrEmpty(value14)) + if (TryGetConfigurationValue(configuration, key: "Prop17", out string? value14)) { - instance.Prop17 = ParseSystemGlobalizationCultureInfo(value14, configuration.GetSection("Prop17").Path); + if (value14 is null) + { + instance.Prop17 = null; + } + else + { + if (!string.IsNullOrEmpty(value14)) + { + instance.Prop17 = ParseSystemGlobalizationCultureInfo(value14, configuration.GetSection("Prop17").Path); + } + } } else if (defaultValueIfNotFound) { @@ -204,54 +250,79 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["Prop19"] is string value15 && !string.IsNullOrEmpty(value15)) + if (TryGetConfigurationValue(configuration, key: "Prop19", out string? value15)) { - instance.Prop19 = ParseSystemDateTime(value15, configuration.GetSection("Prop19").Path); + if (!string.IsNullOrEmpty(value15)) + { + instance.Prop19 = ParseSystemDateTime(value15, configuration.GetSection("Prop19").Path); + } } else if (defaultValueIfNotFound) { instance.Prop19 = instance.Prop19; } - if (configuration["Prop20"] is string value16 && !string.IsNullOrEmpty(value16)) + if (TryGetConfigurationValue(configuration, key: "Prop20", out string? value16)) { - instance.Prop20 = ParseSystemDateTimeOffset(value16, configuration.GetSection("Prop20").Path); + if (!string.IsNullOrEmpty(value16)) + { + instance.Prop20 = ParseSystemDateTimeOffset(value16, configuration.GetSection("Prop20").Path); + } } else if (defaultValueIfNotFound) { instance.Prop20 = instance.Prop20; } - if (configuration["Prop21"] is string value17 && !string.IsNullOrEmpty(value17)) + if (TryGetConfigurationValue(configuration, key: "Prop21", out string? value17)) { - instance.Prop21 = ParseDecimal(value17, configuration.GetSection("Prop21").Path); + if (!string.IsNullOrEmpty(value17)) + { + instance.Prop21 = ParseDecimal(value17, configuration.GetSection("Prop21").Path); + } } else if (defaultValueIfNotFound) { instance.Prop21 = instance.Prop21; } - if (configuration["Prop23"] is string value18 && !string.IsNullOrEmpty(value18)) + if (TryGetConfigurationValue(configuration, key: "Prop23", out string? value18)) { - instance.Prop23 = ParseSystemTimeSpan(value18, configuration.GetSection("Prop23").Path); + if (!string.IsNullOrEmpty(value18)) + { + instance.Prop23 = ParseSystemTimeSpan(value18, configuration.GetSection("Prop23").Path); + } } else if (defaultValueIfNotFound) { instance.Prop23 = instance.Prop23; } - if (configuration["Prop24"] is string value19 && !string.IsNullOrEmpty(value19)) + if (TryGetConfigurationValue(configuration, key: "Prop24", out string? value19)) { - instance.Prop24 = ParseSystemGuid(value19, configuration.GetSection("Prop24").Path); + if (!string.IsNullOrEmpty(value19)) + { + instance.Prop24 = ParseSystemGuid(value19, configuration.GetSection("Prop24").Path); + } } else if (defaultValueIfNotFound) { instance.Prop24 = instance.Prop24; } - if (configuration["Prop25"] is string value20 && !string.IsNullOrEmpty(value20)) + if (TryGetConfigurationValue(configuration, key: "Prop25", out string? value20)) { - instance.Prop25 = ParseSystemUri(value20, configuration.GetSection("Prop25").Path); + if (value20 is null) + { + instance.Prop25 = null; + } + else + { + if (!string.IsNullOrEmpty(value20)) + { + instance.Prop25 = ParseSystemUri(value20, configuration.GetSection("Prop25").Path); + } + } } else if (defaultValueIfNotFound) { @@ -262,9 +333,19 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["Prop26"] is string value21 && !string.IsNullOrEmpty(value21)) + if (TryGetConfigurationValue(configuration, key: "Prop26", out string? value21)) { - instance.Prop26 = ParseSystemVersion(value21, configuration.GetSection("Prop26").Path); + if (value21 is null) + { + instance.Prop26 = null; + } + else + { + if (!string.IsNullOrEmpty(value21)) + { + instance.Prop26 = ParseSystemVersion(value21, configuration.GetSection("Prop26").Path); + } + } } else if (defaultValueIfNotFound) { @@ -275,63 +356,95 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["Prop27"] is string value22 && !string.IsNullOrEmpty(value22)) + if (TryGetConfigurationValue(configuration, key: "Prop27", out string? value22)) { - instance.Prop27 = ParseEnum(value22, configuration.GetSection("Prop27").Path); + if (!string.IsNullOrEmpty(value22)) + { + instance.Prop27 = ParseEnum(value22, configuration.GetSection("Prop27").Path); + } } else if (defaultValueIfNotFound) { instance.Prop27 = instance.Prop27; } - if (configuration["Prop7"] is string value23 && !string.IsNullOrEmpty(value23)) + if (TryGetConfigurationValue(configuration, key: "Prop7", out string? value23)) { - instance.Prop7 = ParseSystemInt128(value23, configuration.GetSection("Prop7").Path); + if (!string.IsNullOrEmpty(value23)) + { + instance.Prop7 = ParseSystemInt128(value23, configuration.GetSection("Prop7").Path); + } } else if (defaultValueIfNotFound) { instance.Prop7 = instance.Prop7; } - if (configuration["Prop11"] is string value24 && !string.IsNullOrEmpty(value24)) + if (TryGetConfigurationValue(configuration, key: "Prop11", out string? value24)) { - instance.Prop11 = ParseSystemHalf(value24, configuration.GetSection("Prop11").Path); + if (!string.IsNullOrEmpty(value24)) + { + instance.Prop11 = ParseSystemHalf(value24, configuration.GetSection("Prop11").Path); + } } else if (defaultValueIfNotFound) { instance.Prop11 = instance.Prop11; } - if (configuration["Prop12"] is string value25 && !string.IsNullOrEmpty(value25)) + if (TryGetConfigurationValue(configuration, key: "Prop12", out string? value25)) { - instance.Prop12 = ParseSystemUInt128(value25, configuration.GetSection("Prop12").Path); + if (!string.IsNullOrEmpty(value25)) + { + instance.Prop12 = ParseSystemUInt128(value25, configuration.GetSection("Prop12").Path); + } } else if (defaultValueIfNotFound) { instance.Prop12 = instance.Prop12; } - if (configuration["Prop18"] is string value26 && !string.IsNullOrEmpty(value26)) + if (TryGetConfigurationValue(configuration, key: "Prop18", out string? value26)) { - instance.Prop18 = ParseSystemDateOnly(value26, configuration.GetSection("Prop18").Path); + if (!string.IsNullOrEmpty(value26)) + { + instance.Prop18 = ParseSystemDateOnly(value26, configuration.GetSection("Prop18").Path); + } } else if (defaultValueIfNotFound) { instance.Prop18 = instance.Prop18; } - if (configuration["Prop22"] is string value27 && !string.IsNullOrEmpty(value27)) + if (TryGetConfigurationValue(configuration, key: "Prop22", out string? value27)) { - instance.Prop22 = ParseSystemTimeOnly(value27, configuration.GetSection("Prop22").Path); + if (!string.IsNullOrEmpty(value27)) + { + instance.Prop22 = ParseSystemTimeOnly(value27, configuration.GetSection("Prop22").Path); + } } else if (defaultValueIfNotFound) { instance.Prop22 = instance.Prop22; } - if (configuration["Prop28"] is string value28 && !string.IsNullOrEmpty(value28)) + if (TryGetConfigurationValue(configuration, key: "Prop28", out string? value28)) { - instance.Prop28 = ParseByteArray(value28, configuration.GetSection("Prop28").Path); + if (value28 is null) + { + instance.Prop28 = null; + } + else + { + if (value28 == string.Empty) + { + instance.Prop28 = ParseByteArray(value28, configuration.GetSection("Prop28").Path); + } + else if (!string.IsNullOrEmpty(value28)) + { + instance.Prop28 = ParseByteArray(value28, configuration.GetSection("Prop28").Path); + } + } } else if (defaultValueIfNotFound) { @@ -342,18 +455,24 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["Prop29"] is string value29 && !string.IsNullOrEmpty(value29)) + if (TryGetConfigurationValue(configuration, key: "Prop29", out string? value29)) { - instance.Prop29 = ParseInt(value29, configuration.GetSection("Prop29").Path); + if (!string.IsNullOrEmpty(value29)) + { + instance.Prop29 = ParseInt(value29, configuration.GetSection("Prop29").Path); + } } else if (defaultValueIfNotFound) { instance.Prop29 = instance.Prop29; } - if (configuration["Prop30"] is string value30 && !string.IsNullOrEmpty(value30)) + if (TryGetConfigurationValue(configuration, key: "Prop30", out string? value30)) { - instance.Prop30 = ParseSystemDateTime(value30, configuration.GetSection("Prop30").Path); + if (!string.IsNullOrEmpty(value30)) + { + instance.Prop30 = ParseSystemDateTime(value30, configuration.GetSection("Prop30").Path); + } } else if (defaultValueIfNotFound) { @@ -361,6 +480,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -392,7 +523,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(T)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(T)}'.", exception); } } @@ -404,7 +535,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(bool)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(bool)}'.", exception); } } @@ -416,7 +547,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(byte)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(byte)}'.", exception); } } @@ -428,7 +559,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(sbyte)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(sbyte)}'.", exception); } } @@ -440,7 +571,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(char)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(char)}'.", exception); } } @@ -452,7 +583,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(double)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(double)}'.", exception); } } @@ -464,7 +595,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } @@ -476,7 +607,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(short)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(short)}'.", exception); } } @@ -488,7 +619,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(long)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(long)}'.", exception); } } @@ -500,7 +631,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(float)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(float)}'.", exception); } } @@ -512,7 +643,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(ushort)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(ushort)}'.", exception); } } @@ -524,7 +655,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(uint)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(uint)}'.", exception); } } @@ -536,7 +667,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(ulong)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(ulong)}'.", exception); } } @@ -548,7 +679,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.Globalization.CultureInfo)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.Globalization.CultureInfo)}'.", exception); } } @@ -560,7 +691,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.DateTime)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.DateTime)}'.", exception); } } @@ -572,7 +703,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.DateTimeOffset)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.DateTimeOffset)}'.", exception); } } @@ -584,7 +715,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(decimal)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(decimal)}'.", exception); } } @@ -596,7 +727,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.TimeSpan)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.TimeSpan)}'.", exception); } } @@ -608,7 +739,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.Guid)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.Guid)}'.", exception); } } @@ -620,7 +751,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.Uri)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.Uri)}'.", exception); } } @@ -632,7 +763,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.Version)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.Version)}'.", exception); } } @@ -644,7 +775,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.Int128)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.Int128)}'.", exception); } } @@ -656,7 +787,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.Half)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.Half)}'.", exception); } } @@ -668,7 +799,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.UInt128)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.UInt128)}'.", exception); } } @@ -680,7 +811,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.DateOnly)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.DateOnly)}'.", exception); } } @@ -692,7 +823,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(global::System.TimeOnly)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.TimeOnly)}'.", exception); } } @@ -704,7 +835,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(byte[])}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(byte[])}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/UnsupportedTypes.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/UnsupportedTypes.generated.txt index 119788b084218a..97da10d781a6fe 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/UnsupportedTypes.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/UnsupportedTypes.generated.txt @@ -91,7 +91,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { instance.Add(value); } @@ -104,7 +104,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (TryGetConfigurationValue(section, key: null, out string? value)) { temp1.Add(value); } @@ -124,7 +124,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(global::Options), s_configKeys_Options, configuration, binderOptions); - if (configuration["Name"] is string value2) + if (TryGetConfigurationValue(configuration, key: "Name", out string? value2)) { instance.Name = value2; } @@ -137,45 +137,55 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (configuration["Age"] is string value3 && !string.IsNullOrEmpty(value3)) + if (TryGetConfigurationValue(configuration, key: "Age", out string? value3)) { - instance.Age = ParseInt(value3, configuration.GetSection("Age").Path); + if (!string.IsNullOrEmpty(value3)) + { + instance.Age = ParseInt(value3, configuration.GetSection("Age").Path); + } } else if (defaultValueIfNotFound) { instance.Age = instance.Age; } - if (AsConfigWithChildren(configuration.GetSection("List")) is IConfigurationSection section4) + var value4 = configuration.GetSection("List"); + if (AsConfigWithChildren(value4) is IConfigurationSection section5) { - global::System.Collections.Generic.List? temp6 = instance.List; - temp6 ??= new global::System.Collections.Generic.List(); - BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); - instance.List = temp6; + global::System.Collections.Generic.List? temp7 = instance.List; + temp7 ??= new global::System.Collections.Generic.List(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.List = temp7; } else { instance.List = instance.List; } - if (AsConfigWithChildren(configuration.GetSection("Array")) is IConfigurationSection section7) + var value8 = configuration.GetSection("Array"); + if (AsConfigWithChildren(value8) is IConfigurationSection section9) { - string[]? temp9 = instance.Array; - temp9 ??= new string[0]; - BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); - instance.Array = temp9; + string[]? temp11 = instance.Array; + temp11 ??= new string[0]; + BindCore(section9, ref temp11, defaultValueIfNotFound: false, binderOptions); + instance.Array = temp11; } else { instance.Array = instance.Array; } + if (instance.Array is null && TryGetConfigurationValue(value8, key: null, out string? value12) && value12 == string.Empty) + { + instance.Array = global::System.Array.Empty(); + } - if (AsConfigWithChildren(configuration.GetSection("Record")) is IConfigurationSection section10) + var value13 = configuration.GetSection("Record"); + if (AsConfigWithChildren(value13) is IConfigurationSection section14) { - global::Record? temp12 = instance.Record; - temp12 ??= InitializeRecordAction(section10, binderOptions); - BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); - instance.Record = temp12; + global::Record? temp16 = instance.Record; + temp16 ??= InitializeRecordAction(section14, binderOptions); + BindCore(section14, ref temp16, defaultValueIfNotFound: false, binderOptions); + instance.Record = temp16; } else { @@ -186,9 +196,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static global::Record InitializeRecordAction(IConfiguration configuration, BinderOptions? binderOptions) { int x = (int)(10); - if (configuration["x"] is string value13 && !string.IsNullOrEmpty(value13)) + if (TryGetConfigurationValue(configuration, key: "x", out string? value17)) { - x = ParseInt(value13, configuration.GetSection("x").Path); + if (!string.IsNullOrEmpty(value17)) + { + x = ParseInt(value17, configuration.GetSection("x").Path); + } } return new global::Record(x) @@ -197,6 +210,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration }; } + /// Tries to get the configuration value for the specified key. + public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) + { + if (configuration is ConfigurationSection section) + { + return section.TryGetValue(key, out value); + } + + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; + return value != null; + } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) @@ -264,7 +289,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{path}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } #endregion Core binding extensions. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/UnitTests/Microsoft.Extensions.Configuration.Binder.Tests.csproj b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/UnitTests/Microsoft.Extensions.Configuration.Binder.Tests.csproj index 82491c7798b9e9..422f8bf1163304 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/UnitTests/Microsoft.Extensions.Configuration.Binder.Tests.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/UnitTests/Microsoft.Extensions.Configuration.Binder.Tests.csproj @@ -7,7 +7,7 @@ - + { }); } + protected override bool SupportNullValues => false; + private void SectionToArgs(List args, string sectionName, TestSection section) { foreach (var tuple in section.Values.SelectMany(e => e.Value.Expand(e.Key))) diff --git a/src/libraries/Microsoft.Extensions.Configuration.Ini/tests/ConfigurationProviderIniTest.cs b/src/libraries/Microsoft.Extensions.Configuration.Ini/tests/ConfigurationProviderIniTest.cs index a914adc3a7d2c0..2ab4f3c97adae8 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Ini/tests/ConfigurationProviderIniTest.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Ini/tests/ConfigurationProviderIniTest.cs @@ -26,6 +26,8 @@ protected override (IConfigurationProvider Provider, Action Initializer) LoadThr return (provider, () => provider.Load(TestStreamHelpers.StringToStream(ini))); } + protected override bool SupportNullValues => false; + private void SectionToIni(StringBuilder iniBuilder, string sectionName, TestSection section) { foreach (var tuple in section.Values.SelectMany(e => e.Value.Expand(e.Key))) diff --git a/src/libraries/Microsoft.Extensions.Configuration.Json/src/JsonConfigurationFileParser.cs b/src/libraries/Microsoft.Extensions.Configuration.Json/src/JsonConfigurationFileParser.cs index fb664a8e5c4412..28e68f1607b148 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Json/src/JsonConfigurationFileParser.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Json/src/JsonConfigurationFileParser.cs @@ -67,7 +67,7 @@ private void VisitArrayElement(JsonElement element) index++; } - SetNullIfElementIsEmpty(isEmpty: index == 0); + SetEmptyIfElementIsEmpty(isEmpty: index == 0); } private void SetNullIfElementIsEmpty(bool isEmpty) @@ -78,6 +78,14 @@ private void SetNullIfElementIsEmpty(bool isEmpty) } } + private void SetEmptyIfElementIsEmpty(bool isEmpty) + { + if (isEmpty && _paths.Count > 0) + { + _data[_paths.Peek()] = string.Empty; + } + } + private void VisitValue(JsonElement value) { Debug.Assert(_paths.Count > 0); @@ -102,7 +110,7 @@ private void VisitValue(JsonElement value) { throw new FormatException(SR.Format(SR.Error_KeyIsDuplicated, key)); } - _data[key] = value.ToString(); + _data[key] = value.ValueKind == JsonValueKind.Null ? null : value.ToString(); break; default: diff --git a/src/libraries/Microsoft.Extensions.Configuration.Json/tests/ArrayTest.cs b/src/libraries/Microsoft.Extensions.Configuration.Json/tests/ArrayTest.cs index a0f25fcc1e5e83..924fafb3cd3551 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Json/tests/ArrayTest.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Json/tests/ArrayTest.cs @@ -22,7 +22,7 @@ public void ArraysAreConvertedToKeyValuePairs() var jsonConfigSource = new JsonConfigurationProvider(new JsonConfigurationSource()); jsonConfigSource.Load(TestStreamHelpers.StringToStream(json)); - + Assert.Equal("1.2.3.4", jsonConfigSource.Get("ip:0")); Assert.Equal("7.8.9.10", jsonConfigSource.Get("ip:1")); Assert.Equal("11.12.13.14", jsonConfigSource.Get("ip:2")); @@ -58,11 +58,11 @@ public void NestedArrays() { var json = @"{ ""ip"": [ - [ + [ ""1.2.3.4"", ""5.6.7.8"" ], - [ + [ ""9.10.11.12"", ""13.14.15.16"" ] @@ -235,11 +235,11 @@ public void TrailingCommas() { var json = @"{ ""ip"": [ - [ + [ ""1.2.3.4"", ""5.6.7.8"", ], - [ + [ ""9.10.11.12"", ""13.14.15.16"", ], @@ -280,7 +280,7 @@ public void EmptyArrayNotIgnored() Assert.Equal(1, config.GetChildren().Count()); Assert.Equal(2, ipSectionChildren.Count()); Assert.Equal("array", ipSectionChildren[0].Key); - Assert.Null(ipSectionChildren[0].Value); + Assert.Equal(string.Empty, ipSectionChildren[0].Value); Assert.Equal("object", ipSectionChildren[1].Key); Assert.Null(ipSectionChildren[1].Value); Assert.Equal(0, ipSectionChildren[0].GetChildren().Count()); diff --git a/src/libraries/Microsoft.Extensions.Configuration.Json/tests/EmptyObjectTest.cs b/src/libraries/Microsoft.Extensions.Configuration.Json/tests/EmptyObjectTest.cs index 5f2f085717a0e4..bf96ab2fce371b 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Json/tests/EmptyObjectTest.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Json/tests/EmptyObjectTest.cs @@ -31,7 +31,7 @@ public void NullObject_AddsEmptyString() var jsonConfigSource = new JsonConfigurationProvider(new JsonConfigurationSource()); jsonConfigSource.Load(TestStreamHelpers.StringToStream(json)); - Assert.Equal("", jsonConfigSource.Get("key")); + Assert.Null(jsonConfigSource.Get("key")); } [Fact] diff --git a/src/libraries/Microsoft.Extensions.Configuration.Json/tests/IntegrationTest.cs b/src/libraries/Microsoft.Extensions.Configuration.Json/tests/IntegrationTest.cs index 3b06c9a50c2bab..c7d6efc8090be2 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Json/tests/IntegrationTest.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Json/tests/IntegrationTest.cs @@ -39,7 +39,7 @@ public void LoadJsonConfiguration() ""h"": {}, ""i"": { ""k"": {} - } + } }"; var configurationBuilder = new ConfigurationBuilder(); @@ -53,7 +53,7 @@ public void LoadJsonConfiguration() x => AssertSection(x, "d", "e"), }), x => AssertSection(x, "f", ""), - x => AssertSection(x, "g", ""), + x => AssertSection(x, "g", null), x => AssertSection(x, "h", null), x => AssertSection(x, "i", null, new Action[] { x => AssertSection(x, "k", null), diff --git a/src/libraries/Microsoft.Extensions.Configuration.Xml/tests/ConfigurationProviderXmlTest.cs b/src/libraries/Microsoft.Extensions.Configuration.Xml/tests/ConfigurationProviderXmlTest.cs index da6d968b089a36..ffc362b52a1d93 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Xml/tests/ConfigurationProviderXmlTest.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Xml/tests/ConfigurationProviderXmlTest.cs @@ -68,6 +68,8 @@ protected override (IConfigurationProvider Provider, Action Initializer) LoadThr return (provider, () => provider.Load(TestStreamHelpers.StringToStream(xml))); } + protected override bool SupportNullValues => false; + private void SectionToXml(StringBuilder xmlBuilder, string sectionName, TestSection section) { xmlBuilder.AppendLine($"<{sectionName}>"); diff --git a/src/libraries/Microsoft.Extensions.Configuration/ref/Microsoft.Extensions.Configuration.cs b/src/libraries/Microsoft.Extensions.Configuration/ref/Microsoft.Extensions.Configuration.cs index 63ba6092812d97..31de97acabf21a 100644 --- a/src/libraries/Microsoft.Extensions.Configuration/ref/Microsoft.Extensions.Configuration.cs +++ b/src/libraries/Microsoft.Extensions.Configuration/ref/Microsoft.Extensions.Configuration.cs @@ -97,6 +97,7 @@ public ConfigurationSection(Microsoft.Extensions.Configuration.IConfigurationRoo public string Key { get { throw null; } } public string Path { get { throw null; } } public string? Value { get { throw null; } set { } } + public bool TryGetValue(string? key, out string? value) { throw null; } public System.Collections.Generic.IEnumerable GetChildren() { throw null; } public Microsoft.Extensions.Primitives.IChangeToken GetReloadToken() { throw null; } public Microsoft.Extensions.Configuration.IConfigurationSection GetSection(string key) { throw null; } diff --git a/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationSection.cs b/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationSection.cs index 7eb7ef75339e68..4ebc121e0b31b2 100644 --- a/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationSection.cs +++ b/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationSection.cs @@ -60,6 +60,25 @@ public string? Value } } + /// + /// Tries to get the value of this section as a string. + /// + /// The configuration key. If null, the value of the section itself is returned. + /// When this method returns, contains the value of the section if it exists; otherwise, null. + /// true if the value was found; otherwise, false. + public bool TryGetValue(string? key, out string? value) + { + string path = key is null ? Path : Path + ConfigurationPath.KeyDelimiter + key; + if (_root.TryGetConfiguration(path, out value)) + { + return true; + } + + // If the section does not exist, return false + value = null; + return false; + } + /// /// Gets or sets the value corresponding to a configuration key. /// diff --git a/src/libraries/Microsoft.Extensions.Configuration/src/InternalConfigurationRootExtensions.cs b/src/libraries/Microsoft.Extensions.Configuration/src/InternalConfigurationRootExtensions.cs index 30859730ea9c8f..30bf4e33752947 100644 --- a/src/libraries/Microsoft.Extensions.Configuration/src/InternalConfigurationRootExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Configuration/src/InternalConfigurationRootExtensions.cs @@ -39,5 +39,20 @@ internal static IEnumerable GetChildrenImplementation(thi return children.ToList(); } } + + internal static bool TryGetConfiguration(this IConfigurationRoot root, string key, out string? value) + { + foreach (IConfigurationProvider provider in root.Providers) + { + if (provider.TryGet(key, out value)) + { + return true; + } + } + + value = null; + return false; + } + } } diff --git a/src/libraries/Microsoft.Extensions.Configuration/tests/ConfigurationProviderTestBase.cs b/src/libraries/Microsoft.Extensions.Configuration/tests/ConfigurationProviderTestBase.cs index c684a89f645bc1..7a29da0f31d4c2 100644 --- a/src/libraries/Microsoft.Extensions.Configuration/tests/ConfigurationProviderTestBase.cs +++ b/src/libraries/Microsoft.Extensions.Configuration/tests/ConfigurationProviderTestBase.cs @@ -46,7 +46,7 @@ public virtual void Has_debug_view() [Fact] public virtual void Null_values_are_included_in_the_config() { - AssertConfig(BuildConfigRoot(LoadThroughProvider(TestSection.NullsTestConfig)), expectNulls: true, nullValue: ""); + AssertConfig(BuildConfigRoot(LoadThroughProvider(TestSection.NullsTestConfig)), expectNulls: true, nullValue: SupportNullValues ? null : string.Empty); } [Fact] @@ -239,7 +239,7 @@ protected virtual void AssertConfig( var section3 = config.GetSection("Section3"); Assert.Equal("Section3", section3.Path, StringComparer.InvariantCultureIgnoreCase); Assert.Null(section3.Value); - + var section4 = config.GetSection("Section3:Section4"); Assert.Equal(value344, section4["Key4"], StringComparer.InvariantCultureIgnoreCase); Assert.Equal("Section3:Section4", section4.Path, StringComparer.InvariantCultureIgnoreCase); @@ -325,6 +325,8 @@ protected virtual void AssertConfig( protected abstract (IConfigurationProvider Provider, Action Initializer) LoadThroughProvider(TestSection testConfig); + protected virtual bool SupportNullValues => true; + protected virtual IConfigurationRoot BuildConfigRoot( params (IConfigurationProvider Provider, Action Initializer)[] providers) {