From 12665882c1e7cb6f3c1f3c55517cb5a3ce5adae2 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Wed, 11 Jun 2025 13:36:05 -0700 Subject: [PATCH 1/4] Support Null configuration --- .../gen/Emitter/CoreBindingHelpers.cs | 216 +++++++--- .../gen/Emitter/ExceptionMessages.cs | 2 +- .../gen/Emitter/Helpers.cs | 2 + .../src/ConfigurationBinder.cs | 75 +++- ...oft.Extensions.Configuration.Binder.csproj | 2 + .../src/Resources/Strings.resx | 58 +-- .../ConfigurationBinderTests.Collections.cs | 23 +- .../ConfigurationBinderTests.TestClasses.cs | 31 ++ .../tests/Common/ConfigurationBinderTests.cs | 389 +++++++++++++++++- .../Version1/Bind.generated.txt | 101 ++++- .../Version1/Bind_Instance.generated.txt | 101 ++++- .../Bind_Instance_BinderOptions.generated.txt | 101 ++++- .../Version1/Bind_Key_Instance.generated.txt | 101 ++++- ...ind_ParseTypeFromMethodParam.generated.txt | 48 +++ .../Version1/Get.generated.txt | 119 ++++-- .../Version1/GetValue.generated.txt | 78 +++- .../Version1/GetValue_T_Key.generated.txt | 60 ++- .../GetValue_T_Key_DefaultValue.generated.txt | 60 ++- .../GetValue_TypeOf_Key.generated.txt | 60 ++- ...alue_TypeOf_Key_DefaultValue.generated.txt | 60 ++- .../Version1/Get_PrimitivesOnly.generated.txt | 80 +++- .../Version1/Get_T.generated.txt | 112 +++-- .../Get_T_BinderOptions.generated.txt | 112 +++-- .../Version1/Get_TypeOf.generated.txt | 57 ++- .../Get_TypeOf_BinderOptions.generated.txt | 57 ++- .../Version1/BindConfiguration.generated.txt | 77 +++- ...gurationWithConfigureActions.generated.txt | 77 +++- .../Version1/Bind_T.generated.txt | 77 +++- .../Bind_T_BinderOptions.generated.txt | 77 +++- .../Version1/Configure_T.generated.txt | 108 +++-- .../Configure_T_BinderOptions.generated.txt | 108 +++-- .../Version1/Configure_T_name.generated.txt | 108 +++-- ...nfigure_T_name_BinderOptions.generated.txt | 108 +++-- .../net462/Version1/Collections.generated.txt | 132 ++++-- ...DefaultConstructorParameters.generated.txt | 190 +++++++-- .../Version1/EmptyConfigType.generated.txt | 51 ++- .../net462/Version1/Primitives.generated.txt | 296 +++++++++---- .../Version1/UnsupportedTypes.generated.txt | 107 +++-- .../Version1/Bind.generated.txt | 101 ++++- .../Version1/Bind_Instance.generated.txt | 101 ++++- .../Bind_Instance_BinderOptions.generated.txt | 101 ++++- .../Version1/Bind_Key_Instance.generated.txt | 101 ++++- ...ind_ParseTypeFromMethodParam.generated.txt | 48 +++ .../Version1/Get.generated.txt | 119 ++++-- .../Version1/GetValue.generated.txt | 78 +++- .../Version1/GetValue_T_Key.generated.txt | 60 ++- .../GetValue_T_Key_DefaultValue.generated.txt | 60 ++- .../GetValue_TypeOf_Key.generated.txt | 60 ++- ...alue_TypeOf_Key_DefaultValue.generated.txt | 60 ++- .../Version1/Get_PrimitivesOnly.generated.txt | 80 +++- .../Version1/Get_T.generated.txt | 112 +++-- .../Get_T_BinderOptions.generated.txt | 112 +++-- .../Version1/Get_TypeOf.generated.txt | 57 ++- .../Get_TypeOf_BinderOptions.generated.txt | 57 ++- .../Version1/BindConfiguration.generated.txt | 77 +++- ...gurationWithConfigureActions.generated.txt | 77 +++- .../Version1/Bind_T.generated.txt | 77 +++- .../Bind_T_BinderOptions.generated.txt | 77 +++- .../Version1/Configure_T.generated.txt | 108 +++-- .../Configure_T_BinderOptions.generated.txt | 108 +++-- .../Version1/Configure_T_name.generated.txt | 108 +++-- ...nfigure_T_name_BinderOptions.generated.txt | 108 +++-- .../Version1/Collections.generated.txt | 132 ++++-- ...DefaultConstructorParameters.generated.txt | 190 +++++++-- .../Version1/EmptyConfigType.generated.txt | 51 ++- .../Version1/Primitives.generated.txt | 341 +++++++++++---- .../Version1/UnsupportedTypes.generated.txt | 107 +++-- .../ConfigBindingGenTestDriver.cs | 5 + ...ation.Binder.SourceGeneration.Tests.csproj | 2 + ...tensions.Configuration.Binder.Tests.csproj | 4 +- .../src/JsonConfigurationFileParser.cs | 13 +- ...osoft.Extensions.Configuration.Json.csproj | 4 + .../tests/ArrayTest.cs | 12 +- .../tests/EmptyObjectTest.cs | 2 +- .../tests/IntegrationTest.cs | 4 +- .../ref/Microsoft.Extensions.Configuration.cs | 1 + .../src/ConfigurationSection.cs | 19 + .../InternalConfigurationRootExtensions.cs | 15 + .../tests/ConfigurationProviderTestBase.cs | 4 +- 79 files changed, 5366 insertions(+), 1208 deletions(-) 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..eeb47ac9862222 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,60 @@ 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}}) + { + var result = {{Identifier.section}}.TryGetValue({{Identifier.key}}, out {{Identifier.value}}); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if ({{Identifier.key}} != null) + { + {{Identifier.value}} = {{Identifier.configuration}}[{{Identifier.key}}]; + } + else + { + {{Identifier.value}} = {{Identifier.configuration}} is {{Identifier.IConfigurationSection}} sec ? sec.Value : null; + } + + return {{Identifier.value}} != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + """); + } + private void EmitValidateConfigurationKeysMethod() { const string keysIdentifier = "keys"; @@ -572,7 +623,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 +693,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 +746,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 +805,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 +813,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 +926,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 +999,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 (!DisallowNullConfigSwitch && {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 +1198,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) + 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) { - writeOnSuccess?.Invoke(defaultValueSource); - } - else - { - _writer.WriteLine($"var currentValue = {defaultValueSource};"); - EmitStartBlock($"if (currentValue is not null)"); - writeOnSuccess?.Invoke("currentValue"); + EmitStartBlock($"if (!DisallowNullConfigSwitch && {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..2b04e670c98dc5 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs @@ -23,6 +23,7 @@ public static class ConfigurationBinder private const string TrimmingWarningMessage = "In case the type is non-primitive, the trimmer cannot statically analyze the object's type so its members may be trimmed."; private const string InstanceGetTypeTrimmingWarningMessage = "Cannot statically analyze the type of instance so its members may be trimmed"; private const string PropertyTrimmingWarningMessage = "Cannot statically analyze property.PropertyType so its members may be trimmed."; + private static bool DisallowNullConfigSwitch { get; } = AppContextSwitchHelper.GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); /// /// Attempts to bind the configuration instance to a new instance of type T. @@ -311,7 +312,7 @@ 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) + if (!propertyBindingPoint.IsReadOnly && (propertyBindingPoint.Value is not null || (propertyBindingPoint.HasNewValue && !DisallowNullConfigSwitch))) { 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 (!DisallowNullConfigSwitch && 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 (!DisallowNullConfigSwitch && (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 && (!DisallowNullConfigSwitch || value != string.Empty)) + { + result = 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..7ce6a0558ad517 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 @@ -15,6 +15,7 @@ + @@ -33,6 +34,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..ffa5d7cfcae513 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 @@ -10,6 +10,7 @@ using Microsoft.Extensions.Configuration; #endif using Xunit; +using Xunit.Abstractions; namespace Microsoft.Extensions #if BUILDING_SOURCE_GENERATOR_TESTS @@ -19,6 +20,10 @@ namespace Microsoft.Extensions { public sealed partial class ConfigurationBinderCollectionTests : ConfigurationBinderTestsBase { + public ConfigurationBinderCollectionTests(ITestOutputHelper output) : base(output) + { + } + [Fact] public void GetList() { @@ -61,7 +66,7 @@ public void GetListNullValues() var list = new List(); config.GetSection("StringList").Bind(list); - Assert.Empty(list); + Assert.Equal(DisallowNullConfigSwitch ? [] : [ null, null, null, null ], list); } [ConditionalFact(typeof(TestHelpers), nameof(TestHelpers.NotSourceGenMode))] // Ensure exception messages are in sync @@ -2182,9 +2187,19 @@ 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)); + if (DisallowNullConfigSwitch) + { + Assert.Equal(2, options.InstantiatedIEnumerable.Count()); + Assert.Equal("Yo1", options.InstantiatedIEnumerable.ElementAt(0)); + Assert.Equal("Yo2", options.InstantiatedIEnumerable.ElementAt(1)); + } + else + { + 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..196bbb8337d977 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs @@ -6,14 +6,18 @@ using System.ComponentModel; using System.Diagnostics; using System.Globalization; +using System.IO; using System.Linq; using System.Reflection; +using System.Text; +using Microsoft.DotNet.RemoteExecutor; #if BUILDING_SOURCE_GENERATOR_TESTS using Microsoft.Extensions.Configuration; #endif using Microsoft.Extensions.Configuration.Memory; using Microsoft.Extensions.Configuration.Test; using Xunit; +using Xunit.Abstractions; namespace Microsoft.Extensions #if BUILDING_SOURCE_GENERATOR_TESTS @@ -23,16 +27,25 @@ namespace Microsoft.Extensions { public abstract class ConfigurationBinderTestsBase { - public ConfigurationBinderTestsBase() + internal readonly ITestOutputHelper _output; + + public ConfigurationBinderTestsBase(ITestOutputHelper output) { + _output = output; #if LAUNCH_DEBUGGER if (!System.Diagnostics.Debugger.IsAttached) { System.Diagnostics.Debugger.Launch(); } #endif } + + public static bool DisallowNullConfigSwitch { get; } = AppContextSwitchHelper.GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); } public partial class ConfigurationBinderTests : ConfigurationBinderTestsBase { + public ConfigurationBinderTests(ITestOutputHelper output) : base(output) + { + } + [Fact] public void BindWithNestedTypesWithReadOnlyProperties() { @@ -208,14 +221,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 +333,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 +384,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 +626,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 +656,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 +1800,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 +2819,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 +2861,350 @@ public void EnsureThrowingWithCollectionAndErrorOnUnknownConfigurationOption() internal class TestSettings { public Dictionary Values { get; init; } = []; } #endif + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + 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); + if (DisallowNullConfigSwitch) + { + Assert.Empty(result.StringProperty2); + Assert.Equal(456, result.IntProperty2); + } + else + { + 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); + + if (DisallowNullConfigSwitch) + { + Assert.Equal("Initial Value 2", inMemoryResult.StringProperty2); + Assert.Equal(456, inMemoryResult.IntProperty2); + } + else + { + Assert.Null(inMemoryResult.StringProperty2); + Assert.Null(inMemoryResult.IntProperty2); + } + Assert.Equal("", inMemoryResult.StringProperty3); + Assert.Equal(42, inMemoryResult.IntProperty1); + + // + // Test with forcing the config switch on + // + + ProcessStartInfo psi = new ProcessStartInfo() { UseShellExecute = false }; + psi.Environment.Add("DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL", "true"); + + RemoteExecutor.Invoke((jsonConfiguration) => + { + var configuration1 = new ConfigurationBuilder() + .AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(jsonConfiguration))) + .Build().GetSection("NullConfiguration"); + + bool b = (bool)typeof(ConfigurationBinder).GetProperty("DisallowNullConfigSwitch", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null); + Assert.True(b, "DisallowNullConfigSwitch should be true when DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL is set to true."); + NullConfiguration result1 = new NullConfiguration(); + result1.IntProperty2 = 10; // Initialize to non-null so it should fail after binding. + Assert.Equal(10, result1.IntProperty2); + + configuration1.Bind(result1); + + Assert.Equal("New Value!", result1.StringProperty1); + Assert.Equal("", result1.StringProperty2); // Compatibility case: empty string is used for null value. + Assert.Equal("", result1.StringProperty3); + + Assert.Equal(42, result1.IntProperty1); + Assert.Equal(10, result1.IntProperty2); // Compatibility case: empty string is used for null value. No binding done in that case for int? type. + + + var inMemoryConfiguration1 = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + { "NullConfiguration:StringProperty1", "New Value!" }, + { "NullConfiguration:StringProperty2", null }, + { "NullConfiguration:StringProperty3", "" }, + { "NullConfiguration:IntProperty1", "42" }, + { "NullConfiguration:IntProperty2", null } + }) + .Build().GetSection("NullConfiguration"); + + NullConfiguration inMemoryResult1 = inMemoryConfiguration1.Get(); + + Assert.NotNull(inMemoryResult1); + Assert.Equal("New Value!", inMemoryResult1.StringProperty1); + Assert.Equal("Initial Value 2", inMemoryResult1.StringProperty2); // Compatibility case: null value treated as missing and not bound. + Assert.Equal("", inMemoryResult1.StringProperty3); + Assert.Equal(42, inMemoryResult1.IntProperty1); + Assert.Equal(456, inMemoryResult1.IntProperty2); // Compatibility case: null value treated as missing and not bound. + }, jsonConfig, new RemoteInvokeOptions { StartInfo = psi }).Dispose(); + } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + 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); + + if (DisallowNullConfigSwitch) + { + Assert.Null(instance.StringArray3); + Assert.Null(instance.ByteArray2); // empty string should result in empty array + } + else + { + 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); + if (DisallowNullConfigSwitch) + { + Assert.Null(instance.StringArray3); // empty string should result in empty array + Assert.Null(instance.ByteArray2); // empty string should result in empty array + } + else + { + 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); + if (DisallowNullConfigSwitch) + { + Assert.Null(inMemoryInstance.StringArray3); // empty string should result in empty array + Assert.Null(inMemoryInstance.ByteArray2); // empty string should result in empty array + } + else + { + 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); + if (DisallowNullConfigSwitch) + { + Assert.Null(inMemoryInstance.StringArray3); // empty string should result in empty array + Assert.Null(inMemoryInstance.ByteArray2); // empty string should result in empty array + } + else + { + 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 + // + // Test with the config switch on + // + + ProcessStartInfo psi = new ProcessStartInfo() { UseShellExecute = false }; + psi.Environment.Add("DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL", "true"); + + RemoteExecutor.Invoke((jsonConfiguration) => + { + var configuration1 = new ConfigurationBuilder() + .AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(jsonConfiguration))) + .Build().GetSection("ArraysContainer"); + + ArraysContainer instance1 = new(); // all properties are initialized to null. + configuration1.Bind(instance1); + + Assert.NotNull(instance1); + Assert.Equal(["Value1", "Value2"], instance1.StringArray1); + Assert.Null(instance1.StringArray2); + Assert.Null(instance1.StringArray3); + + Assert.Null(instance1.ByteArray1); + Assert.Null(instance1.ByteArray2); + Assert.Equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], instance1.ByteArray3); + + // Bind one more time and ensure the values are accumulated correctly. + configuration1.Bind(instance1); + Assert.Equal(["Value1", "Value2", "Value1", "Value2"], instance1.StringArray1); + Assert.Null(instance1.StringArray2); + Assert.Null(instance1.StringArray3); // empty string should result in empty array + + Assert.Null(instance1.ByteArray1); + Assert.Null(instance1.ByteArray2); // empty string should result in empty array +#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], instance1.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], instance1.ByteArray3); +#endif + // Test the same accumulation behavior with in-memory configuration + var inMemoryConfiguration1 = 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 inMemoryInstance1 = new(); + inMemoryConfiguration1.Bind(inMemoryInstance1); + Assert.Equal(["Value1", "Value2"], inMemoryInstance1.StringArray1); + Assert.Null(inMemoryInstance1.StringArray2); + Assert.Null(inMemoryInstance1.StringArray3); // empty string should result in empty array + Assert.Null(inMemoryInstance1.ByteArray2); // empty string should result in empty array + + Assert.Null(inMemoryInstance1.ByteArray1); + Assert.Equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], inMemoryInstance1.ByteArray3); + + // Bind one more time and ensure the values are accumulated correctly. + inMemoryConfiguration1.Bind(inMemoryInstance1); + Assert.Equal(["Value1", "Value2", "Value1", "Value2"], inMemoryInstance1.StringArray1); + Assert.Null(inMemoryInstance1.StringArray2); + Assert.Null(inMemoryInstance1.StringArray3); // empty string should result in empty array + Assert.Null(inMemoryInstance1.ByteArray2); // empty string should result in empty array + + Assert.Null(inMemoryInstance1.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], inMemoryInstance1.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], inMemoryInstance1.ByteArray3); +#endif + }, jsonConfig, new RemoteInvokeOptions { StartInfo = psi }).Dispose(); + } + + [Fact] + public void DisplayDisallowNullConfigSwitchValue() + { + _output.WriteLine($"DisallowNullConfigSwitch: {DisallowNullConfigSwitch}"); + Console.WriteLine($"DisallowNullConfigSwitch: {DisallowNullConfigSwitch}"); + } } } 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..32e9ba1145d037 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +304,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..719aa68f27539b 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +250,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..94847e9eb6193d 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +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_Key_Instance.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Key_Instance.generated.txt index aa3c6a9e943107..16e1dc12bb135d 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +250,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..ff3799e1291993 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + 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..ab449992c9d462 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 (!DisallowNullConfigSwitch && 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +335,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..f414de9743d44b 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,75 @@ 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 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) { - return ParseInt(value, section.Path); + value = configuration[key]; } - else if (type == typeof(bool?)) + else { - return ParseBool(value, section.Path); + value = configuration is IConfigurationSection sec ? sec.Value : null; } - else if (type == typeof(byte[])) + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) { - return ParseByteArray(value, section.Path); + return value; } - else if (type == typeof(global::System.Globalization.CultureInfo)) + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) { - return ParseSystemGlobalizationCultureInfo(value, section.Path); + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } } - - return null; + + return defaultValue; } public static int ParseInt(string value, string? path) @@ -93,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(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } @@ -105,7 +151,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 +163,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 +175,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..f24baafee55723 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,63 @@ 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); + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } } - - return null; + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; } public static int ParseInt(string value, string? path) @@ -69,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(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..ee431b4e8533e4 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,63 @@ 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); + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } } - - return null; + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; } public static int ParseInt(string value, string? path) @@ -69,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(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..d0d3037d4dd445 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,63 @@ 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); + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } } - - return null; + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; } public static bool ParseBool(string value, string? path) @@ -69,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); } } #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..dab5ef6e4004c1 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,63 @@ 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); + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } } - - return null; + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; } public static global::System.Globalization.CultureInfo ParseSystemGlobalizationCultureInfo(string value, string? path) @@ -69,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(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..3523381ae4886c 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,66 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + public static bool HasValueOrChildren(IConfiguration configuration) { if ((configuration as IConfigurationSection)?.Value is not null) @@ -154,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); } } @@ -166,7 +226,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 +238,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..985317c13eaba7 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 (!DisallowNullConfigSwitch && 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +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/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..1679172ae1e9f6 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 (!DisallowNullConfigSwitch && 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +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/Get_TypeOf.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_TypeOf.generated.txt index 589f4b4fb10d93..118fe52fa5933d 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +198,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..ce5cba8fa2864c 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +198,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..1cfc24851a3c2d 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +269,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..b41fc3ff61cf75 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +269,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..3d5c412490f966 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +275,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..3c69c87c2d6485 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +269,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..728e0aee54778b 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +331,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..4e1ae4acb45486 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +331,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..269a91865f6f36 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +331,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..e3c1859c6e9523 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +325,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..75841b437dc797 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +329,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..0b1f9e2c6ee1e6 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,218 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +307,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 +319,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 +331,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 +343,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 +355,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 +367,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..b83ffde885b544 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,61 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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..16e27c71581733 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 (!DisallowNullConfigSwitch && 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +502,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 +514,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 +526,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 +538,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 +550,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 +562,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 +574,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 +586,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 +598,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 +610,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 +622,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 +634,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 +646,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 +658,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 +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.DateTime)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.DateTime)}'.", exception); } } @@ -530,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.DateTimeOffset)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.DateTimeOffset)}'.", exception); } } @@ -542,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(decimal)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(decimal)}'.", exception); } } @@ -554,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.TimeSpan)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.TimeSpan)}'.", exception); } } @@ -566,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(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 +730,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 +742,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 +754,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..af4840c0e743ee 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 (!DisallowNullConfigSwitch && 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +331,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..26e2920525bc1c 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +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_Instance.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Instance.generated.txt index 43edcd50559144..efb2b504b25ce1 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +247,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..509c1e04721496 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +265,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..e5f5505381b25f 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +247,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..ff3799e1291993 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + 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..c59f8ce08c3c2a 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 (!DisallowNullConfigSwitch && 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +332,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..0a7995cb2323a1 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,75 @@ 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 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) { - return ParseInt(value, section.Path); + value = configuration[key]; } - else if (type == typeof(bool?)) + else { - return ParseBool(value, section.Path); + value = configuration is IConfigurationSection sec ? sec.Value : null; } - else if (type == typeof(byte[])) + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) { - return ParseByteArray(value, section.Path); + return value; } - else if (type == typeof(global::System.Globalization.CultureInfo)) + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) { - return ParseSystemGlobalizationCultureInfo(value, section.Path); + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } } - - return null; + + return defaultValue; } public static int ParseInt(string value, string? path) @@ -92,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(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(int)}'.", exception); } } @@ -104,7 +150,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 +162,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 +174,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..80ed7100ab82ce 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,63 @@ 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); + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } } - - return null; + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; } public static int ParseInt(string value, string? path) @@ -66,7 +112,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..5335522e633c0c 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,63 @@ 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); + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } } - - return null; + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; } public static int ParseInt(string value, string? path) @@ -67,7 +113,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..d9aff81b9f5ecb 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,63 @@ 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); + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } } - - return null; + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; } public static bool ParseBool(string value, string? path) @@ -66,7 +112,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..d0b5cdb21004cf 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,63 @@ 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); + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } } - - return null; + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; } public static global::System.Globalization.CultureInfo ParseSystemGlobalizationCultureInfo(string value, string? path) @@ -67,7 +113,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..255b4aa609d82f 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,66 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + public static bool HasValueOrChildren(IConfiguration configuration) { if ((configuration as IConfigurationSection)?.Value is not null) @@ -151,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); } } @@ -163,7 +223,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 +235,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..b5d4c253fde199 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 (!DisallowNullConfigSwitch && 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +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/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..cddb35bbc0b5a7 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 (!DisallowNullConfigSwitch && 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +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/Get_TypeOf.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_TypeOf.generated.txt index 4ed9fffb984230..47ed4e2ef9726d 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +195,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..d2e297e59f2030 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +195,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..9fb2ceff7cd86b 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +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/OptionsBuilder/Version1/BindConfigurationWithConfigureActions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/BindConfigurationWithConfigureActions.generated.txt index 52d92bd33dffea..08c998543d6fff 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +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/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..bc1048a9b0a8ef 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +266,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..913945f2f27013 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +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/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..a7a90d9c91ba9d 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +325,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..1d53fcb8148ec7 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +325,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..305164fd0ff18d 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +325,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..a32098d4d63242 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +319,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..5293110c3f591e 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +326,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..e0024bebe7297c 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,218 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +304,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 +316,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 +328,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 +340,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 +352,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 +364,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..6eb9f2860ce5df 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,61 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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..6ee203061bbdc0 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 (!DisallowNullConfigSwitch && 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +559,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 +571,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 +583,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 +595,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 +607,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 +619,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 +631,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 +643,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 +655,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 +667,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 +679,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 +691,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 +703,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 +715,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 +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.DateTime)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.DateTime)}'.", exception); } } @@ -572,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.DateTimeOffset)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.DateTimeOffset)}'.", exception); } } @@ -584,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(decimal)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(decimal)}'.", exception); } } @@ -596,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.TimeSpan)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.TimeSpan)}'.", exception); } } @@ -608,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.Guid)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.Guid)}'.", exception); } } @@ -620,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.Uri)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.Uri)}'.", exception); } } @@ -632,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.Version)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.Version)}'.", exception); } } @@ -644,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.Int128)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.Int128)}'.", exception); } } @@ -656,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.Half)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value '{value ?? "null"}' at '{path}' to type '{typeof(global::System.Half)}'.", exception); } } @@ -668,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(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 +847,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 +859,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 +871,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..3547fc8bd3011e 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 (!DisallowNullConfigSwitch && 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,54 @@ 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) + { + var result = section.TryGetValue(key, out value); + if (!DisallowNullConfigSwitch) + { + return result; + } + } + else if (key != null) + { + value = configuration[key]; + } + else + { + value = configuration is IConfigurationSection sec ? sec.Value : null; + } + + return value != null; + } + + /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. + private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); + + private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) + { + if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) + { + return value; + } + + if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) + { + if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return defaultValue; + } + /// 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 +325,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/ConfigBindingGenTestDriver.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs index b15815d72527fc..41797c8fc52695 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs @@ -15,12 +15,17 @@ using Microsoft.Extensions.Configuration.Binder.SourceGeneration; using SourceGenerators.Tests; using Xunit; +using Xunit.Abstractions; namespace Microsoft.Extensions.SourceGeneration.Configuration.Binder.Tests { [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] public partial class ConfigurationBindingGeneratorTests : ConfigurationBinderTestsBase { + public ConfigurationBindingGeneratorTests(ITestOutputHelper output) : base(output) + { + } + internal sealed class ConfigBindingGenTestDriver { private readonly CSharpParseOptions _parseOptions; diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests.csproj b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests.csproj index ddc4d17d10f019..fb0ba94686233f 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests.csproj @@ -10,6 +10,7 @@ $(InterceptorsPreviewNamespaces);Microsoft.Extensions.Configuration.Binder.SourceGeneration true ..\..\src\Resources\Strings.resx + true @@ -25,6 +26,7 @@ + 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..c1aec5bfdd5442 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 @@ -4,14 +4,16 @@ $(NetCoreAppCurrent);$(NetFrameworkCurrent) true ..\..\src\Resources\Strings.resx + true - + + diff --git a/src/libraries/Microsoft.Extensions.Configuration.Json/src/JsonConfigurationFileParser.cs b/src/libraries/Microsoft.Extensions.Configuration.Json/src/JsonConfigurationFileParser.cs index fb664a8e5c4412..c3fd6b04a2a027 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Json/src/JsonConfigurationFileParser.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Json/src/JsonConfigurationFileParser.cs @@ -15,6 +15,7 @@ private JsonConfigurationFileParser() { } private readonly Dictionary _data = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly Stack _paths = new Stack(); + private static bool DisallowNullConfigSwitch { get; } = AppContextSwitchHelper.GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); public static IDictionary Parse(Stream input) => new JsonConfigurationFileParser().ParseStream(input); @@ -67,7 +68,7 @@ private void VisitArrayElement(JsonElement element) index++; } - SetNullIfElementIsEmpty(isEmpty: index == 0); + SetEmptyIfElementIsEmpty(isEmpty: index == 0); } private void SetNullIfElementIsEmpty(bool isEmpty) @@ -78,6 +79,14 @@ private void SetNullIfElementIsEmpty(bool isEmpty) } } + private void SetEmptyIfElementIsEmpty(bool isEmpty) + { + if (isEmpty && _paths.Count > 0) + { + _data[_paths.Peek()] = DisallowNullConfigSwitch ? null : string.Empty; + } + } + private void VisitValue(JsonElement value) { Debug.Assert(_paths.Count > 0); @@ -102,7 +111,7 @@ private void VisitValue(JsonElement value) { throw new FormatException(SR.Format(SR.Error_KeyIsDuplicated, key)); } - _data[key] = value.ToString(); + _data[key] = !DisallowNullConfigSwitch && value.ValueKind == JsonValueKind.Null ? null : value.ToString(); break; default: diff --git a/src/libraries/Microsoft.Extensions.Configuration.Json/src/Microsoft.Extensions.Configuration.Json.csproj b/src/libraries/Microsoft.Extensions.Configuration.Json/src/Microsoft.Extensions.Configuration.Json.csproj index 1b1d2e7a165a4b..1ce1ea83ba67af 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Json/src/Microsoft.Extensions.Configuration.Json.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration.Json/src/Microsoft.Extensions.Configuration.Json.csproj @@ -8,6 +8,10 @@ JSON configuration provider implementation for Microsoft.Extensions.Configuration. This package enables you to read your application's settings from a JSON file. You can use JsonConfigurationExtensions.AddJsonFile extension method on IConfigurationBuilder to add the JSON configuration provider to the configuration builder. + + + + 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/ref/Microsoft.Extensions.Configuration.cs b/src/libraries/Microsoft.Extensions.Configuration/ref/Microsoft.Extensions.Configuration.cs index 63ba6092812d97..dc6ce758d2b53c 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 virtual 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..eddbb15e8df8b7 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 virtual 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..4658330ab45f36 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: null); } [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); From d008482f29aad68125600914ee2ae6df9501f204 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Tue, 17 Jun 2025 18:21:32 -0700 Subject: [PATCH 2/4] Remove the config switch --- .../gen/Emitter/CoreBindingHelpers.cs | 44 +--- .../src/ConfigurationBinder.cs | 11 +- ...oft.Extensions.Configuration.Binder.csproj | 1 - .../ConfigurationBinderTests.Collections.cs | 21 +- .../tests/Common/ConfigurationBinderTests.cs | 228 ++---------------- .../Version1/Bind.generated.txt | 40 +-- .../Version1/Bind_Instance.generated.txt | 40 +-- .../Bind_Instance_BinderOptions.generated.txt | 40 +-- .../Version1/Bind_Key_Instance.generated.txt | 40 +-- ...ind_ParseTypeFromMethodParam.generated.txt | 40 +-- .../Version1/Get.generated.txt | 42 +--- .../Version1/GetValue.generated.txt | 40 +-- .../Version1/GetValue_T_Key.generated.txt | 40 +-- .../GetValue_T_Key_DefaultValue.generated.txt | 40 +-- .../GetValue_TypeOf_Key.generated.txt | 40 +-- ...alue_TypeOf_Key_DefaultValue.generated.txt | 40 +-- .../Version1/Get_PrimitivesOnly.generated.txt | 40 +-- .../Version1/Get_T.generated.txt | 42 +--- .../Get_T_BinderOptions.generated.txt | 42 +--- .../Version1/Get_TypeOf.generated.txt | 40 +-- .../Get_TypeOf_BinderOptions.generated.txt | 40 +-- .../Version1/BindConfiguration.generated.txt | 40 +-- ...gurationWithConfigureActions.generated.txt | 40 +-- .../Version1/Bind_T.generated.txt | 40 +-- .../Bind_T_BinderOptions.generated.txt | 40 +-- .../Version1/Configure_T.generated.txt | 40 +-- .../Configure_T_BinderOptions.generated.txt | 40 +-- .../Version1/Configure_T_name.generated.txt | 40 +-- ...nfigure_T_name_BinderOptions.generated.txt | 40 +-- .../net462/Version1/Collections.generated.txt | 40 +-- ...DefaultConstructorParameters.generated.txt | 40 +-- .../Version1/EmptyConfigType.generated.txt | 40 +-- .../net462/Version1/Primitives.generated.txt | 42 +--- .../Version1/UnsupportedTypes.generated.txt | 42 +--- .../Version1/Bind.generated.txt | 40 +-- .../Version1/Bind_Instance.generated.txt | 40 +-- .../Bind_Instance_BinderOptions.generated.txt | 40 +-- .../Version1/Bind_Key_Instance.generated.txt | 40 +-- ...ind_ParseTypeFromMethodParam.generated.txt | 40 +-- .../Version1/Get.generated.txt | 42 +--- .../Version1/GetValue.generated.txt | 40 +-- .../Version1/GetValue_T_Key.generated.txt | 40 +-- .../GetValue_T_Key_DefaultValue.generated.txt | 40 +-- .../GetValue_TypeOf_Key.generated.txt | 40 +-- ...alue_TypeOf_Key_DefaultValue.generated.txt | 40 +-- .../Version1/Get_PrimitivesOnly.generated.txt | 40 +-- .../Version1/Get_T.generated.txt | 42 +--- .../Get_T_BinderOptions.generated.txt | 42 +--- .../Version1/Get_TypeOf.generated.txt | 40 +-- .../Get_TypeOf_BinderOptions.generated.txt | 40 +-- .../Version1/BindConfiguration.generated.txt | 40 +-- ...gurationWithConfigureActions.generated.txt | 40 +-- .../Version1/Bind_T.generated.txt | 40 +-- .../Bind_T_BinderOptions.generated.txt | 40 +-- .../Version1/Configure_T.generated.txt | 40 +-- .../Configure_T_BinderOptions.generated.txt | 40 +-- .../Version1/Configure_T_name.generated.txt | 40 +-- ...nfigure_T_name_BinderOptions.generated.txt | 40 +-- .../Version1/Collections.generated.txt | 40 +-- ...DefaultConstructorParameters.generated.txt | 40 +-- .../Version1/EmptyConfigType.generated.txt | 40 +-- .../Version1/Primitives.generated.txt | 42 +--- .../Version1/UnsupportedTypes.generated.txt | 42 +--- .../ConfigBindingGenTestDriver.cs | 2 +- ...ation.Binder.SourceGeneration.Tests.csproj | 1 - ...tensions.Configuration.Binder.Tests.csproj | 1 - .../src/JsonConfigurationFileParser.cs | 5 +- ...osoft.Extensions.Configuration.Json.csproj | 4 - .../ref/Microsoft.Extensions.Configuration.cs | 2 +- .../src/ConfigurationSection.cs | 2 +- 70 files changed, 163 insertions(+), 2499 deletions(-) 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 eeb47ac9862222..8001e8f9175c0f 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs @@ -494,48 +494,12 @@ private void EmitTryGetConfigurationValueMethod() { if ({{Identifier.configuration}} is {{Identifier.ConfigurationSection}} {{Identifier.section}}) { - var result = {{Identifier.section}}.TryGetValue({{Identifier.key}}, out {{Identifier.value}}); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if ({{Identifier.key}} != null) - { - {{Identifier.value}} = {{Identifier.configuration}}[{{Identifier.key}}]; - } - else - { - {{Identifier.value}} = {{Identifier.configuration}} is {{Identifier.IConfigurationSection}} sec ? sec.Value : null; + 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; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } """); } @@ -1015,7 +979,7 @@ complexType is not CollectionSpec && if (complexType is ArraySpec arraySpec && canSet) { string valueIdentifier = GetIncrementalIdentifier(Identifier.value); - EmitStartBlock($@"if (!DisallowNullConfigSwitch && {memberAccessExpr} is null && {Identifier.TryGetConfigurationValue}({configSection}, {Identifier.key}: null, out string? {valueIdentifier}) && {valueIdentifier} == string.Empty)"); + 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(); } @@ -1223,7 +1187,7 @@ private void EmitBindingLogic( // Special case ByteArray when having empty string configuration value as we need to assign empty byte array at that time. if (typeKind == StringParsableTypeKind.ByteArray) { - EmitStartBlock($"if (!DisallowNullConfigSwitch && {sectionValueExpr} == string.Empty)"); + EmitStartBlock($"if ({sectionValueExpr} == string.Empty)"); writeOnSuccess?.Invoke(parsedValueExpr); EmitEndBlock(); conditionPrefix = "else "; diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs index 2b04e670c98dc5..9463b1c95396f8 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs @@ -23,7 +23,6 @@ public static class ConfigurationBinder private const string TrimmingWarningMessage = "In case the type is non-primitive, the trimmer cannot statically analyze the object's type so its members may be trimmed."; private const string InstanceGetTypeTrimmingWarningMessage = "Cannot statically analyze the type of instance so its members may be trimmed"; private const string PropertyTrimmingWarningMessage = "Cannot statically analyze property.PropertyType so its members may be trimmed."; - private static bool DisallowNullConfigSwitch { get; } = AppContextSwitchHelper.GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); /// /// Attempts to bind the configuration instance to a new instance of type T. @@ -312,7 +311,7 @@ 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 || (propertyBindingPoint.HasNewValue && !DisallowNullConfigSwitch))) + if (!propertyBindingPoint.IsReadOnly && (propertyBindingPoint.Value is not null || (propertyBindingPoint.HasNewValue))) { property.SetValue(instance, propertyBindingPoint.Value); } @@ -343,7 +342,7 @@ private static void BindInstance( string? configValue; bool isConfigurationExist; - if (!DisallowNullConfigSwitch && config is ConfigurationSection configSection) + if (config is ConfigurationSection configSection) { section = configSection; isConfigurationExist = configSection.TryGetValue(key:null, out configValue); @@ -516,7 +515,7 @@ private static void BindInstance( else if (isConfigurationExist && bindingPoint.Value is null) { // Don't override the existing array in bindingPoint.Value if it is already set. - if (!DisallowNullConfigSwitch && (type.IsArray || IsImmutableArrayCompatibleInterface(type))) + 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)); @@ -1013,9 +1012,9 @@ private static bool TryConvertValue( { try { - if (value is not null && (!DisallowNullConfigSwitch || value != string.Empty)) + if (value is not null ) { - result = Convert.FromBase64String(value); + result = value == string.Empty ? Array.Empty() : Convert.FromBase64String(value); } } catch (FormatException ex) 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 7ce6a0558ad517..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 @@ -15,7 +15,6 @@ - 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 ffa5d7cfcae513..ef792c7c429375 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 @@ -20,7 +20,7 @@ namespace Microsoft.Extensions { public sealed partial class ConfigurationBinderCollectionTests : ConfigurationBinderTestsBase { - public ConfigurationBinderCollectionTests(ITestOutputHelper output) : base(output) + public ConfigurationBinderCollectionTests() : base() { } @@ -66,7 +66,7 @@ public void GetListNullValues() var list = new List(); config.GetSection("StringList").Bind(list); - Assert.Equal(DisallowNullConfigSwitch ? [] : [ null, null, null, null ], list); + Assert.Equal([ null, null, null, null ], list); } [ConditionalFact(typeof(TestHelpers), nameof(TestHelpers.NotSourceGenMode))] // Ensure exception messages are in sync @@ -2187,19 +2187,10 @@ public void CanBindInstantiatedIEnumerableWithNullItems() var options = config.Get()!; - if (DisallowNullConfigSwitch) - { - Assert.Equal(2, options.InstantiatedIEnumerable.Count()); - Assert.Equal("Yo1", options.InstantiatedIEnumerable.ElementAt(0)); - Assert.Equal("Yo2", options.InstantiatedIEnumerable.ElementAt(1)); - } - else - { - 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)); - } + 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.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs index 196bbb8337d977..fb72273b1c230d 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs @@ -10,7 +10,6 @@ using System.Linq; using System.Reflection; using System.Text; -using Microsoft.DotNet.RemoteExecutor; #if BUILDING_SOURCE_GENERATOR_TESTS using Microsoft.Extensions.Configuration; #endif @@ -27,22 +26,17 @@ namespace Microsoft.Extensions { public abstract class ConfigurationBinderTestsBase { - internal readonly ITestOutputHelper _output; - - public ConfigurationBinderTestsBase(ITestOutputHelper output) + public ConfigurationBinderTestsBase() { - _output = output; #if LAUNCH_DEBUGGER if (!System.Diagnostics.Debugger.IsAttached) { System.Diagnostics.Debugger.Launch(); } #endif } - - public static bool DisallowNullConfigSwitch { get; } = AppContextSwitchHelper.GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); } public partial class ConfigurationBinderTests : ConfigurationBinderTestsBase { - public ConfigurationBinderTests(ITestOutputHelper output) : base(output) + public ConfigurationBinderTests() : base() { } @@ -2862,7 +2856,7 @@ public void EnsureThrowingWithCollectionAndErrorOnUnknownConfigurationOption() internal class TestSettings { public Dictionary Values { get; init; } = []; } #endif - [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + [Fact] public void BindWithNullValues() { // @@ -2889,16 +2883,8 @@ public void BindWithNullValues() Assert.NotNull(result); Assert.Equal("New Value!", result.StringProperty1); - if (DisallowNullConfigSwitch) - { - Assert.Empty(result.StringProperty2); - Assert.Equal(456, result.IntProperty2); - } - else - { - Assert.Null(result.StringProperty2); - Assert.Null(result.IntProperty2); - } + Assert.Null(result.StringProperty2); + Assert.Null(result.IntProperty2); Assert.Equal("", result.StringProperty3); Assert.Equal(42, result.IntProperty1); @@ -2924,71 +2910,13 @@ public void BindWithNullValues() Assert.Equal("New Value!", inMemoryResult.StringProperty1); - if (DisallowNullConfigSwitch) - { - Assert.Equal("Initial Value 2", inMemoryResult.StringProperty2); - Assert.Equal(456, inMemoryResult.IntProperty2); - } - else - { - Assert.Null(inMemoryResult.StringProperty2); - Assert.Null(inMemoryResult.IntProperty2); - } + Assert.Null(inMemoryResult.StringProperty2); + Assert.Null(inMemoryResult.IntProperty2); Assert.Equal("", inMemoryResult.StringProperty3); Assert.Equal(42, inMemoryResult.IntProperty1); + } - // - // Test with forcing the config switch on - // - - ProcessStartInfo psi = new ProcessStartInfo() { UseShellExecute = false }; - psi.Environment.Add("DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL", "true"); - - RemoteExecutor.Invoke((jsonConfiguration) => - { - var configuration1 = new ConfigurationBuilder() - .AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(jsonConfiguration))) - .Build().GetSection("NullConfiguration"); - - bool b = (bool)typeof(ConfigurationBinder).GetProperty("DisallowNullConfigSwitch", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null); - Assert.True(b, "DisallowNullConfigSwitch should be true when DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL is set to true."); - NullConfiguration result1 = new NullConfiguration(); - result1.IntProperty2 = 10; // Initialize to non-null so it should fail after binding. - Assert.Equal(10, result1.IntProperty2); - - configuration1.Bind(result1); - - Assert.Equal("New Value!", result1.StringProperty1); - Assert.Equal("", result1.StringProperty2); // Compatibility case: empty string is used for null value. - Assert.Equal("", result1.StringProperty3); - - Assert.Equal(42, result1.IntProperty1); - Assert.Equal(10, result1.IntProperty2); // Compatibility case: empty string is used for null value. No binding done in that case for int? type. - - - var inMemoryConfiguration1 = new ConfigurationBuilder() - .AddInMemoryCollection(new Dictionary - { - { "NullConfiguration:StringProperty1", "New Value!" }, - { "NullConfiguration:StringProperty2", null }, - { "NullConfiguration:StringProperty3", "" }, - { "NullConfiguration:IntProperty1", "42" }, - { "NullConfiguration:IntProperty2", null } - }) - .Build().GetSection("NullConfiguration"); - - NullConfiguration inMemoryResult1 = inMemoryConfiguration1.Get(); - - Assert.NotNull(inMemoryResult1); - Assert.Equal("New Value!", inMemoryResult1.StringProperty1); - Assert.Equal("Initial Value 2", inMemoryResult1.StringProperty2); // Compatibility case: null value treated as missing and not bound. - Assert.Equal("", inMemoryResult1.StringProperty3); - Assert.Equal(42, inMemoryResult1.IntProperty1); - Assert.Equal(456, inMemoryResult1.IntProperty2); // Compatibility case: null value treated as missing and not bound. - }, jsonConfig, new RemoteInvokeOptions { StartInfo = psi }).Dispose(); - } - - [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + [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. @@ -3019,16 +2947,8 @@ public void BindArraysWithNullAndOtherValues() Assert.Equal(["Value1", "Value2"], instance.StringArray1); Assert.Null(instance.StringArray2); - if (DisallowNullConfigSwitch) - { - Assert.Null(instance.StringArray3); - Assert.Null(instance.ByteArray2); // empty string should result in empty array - } - else - { - Assert.Empty(instance.StringArray3); // empty string should result in empty array - Assert.Empty(instance.ByteArray2); // empty string should result in empty array - } + 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); @@ -3037,16 +2957,8 @@ public void BindArraysWithNullAndOtherValues() configuration.Bind(instance); Assert.Equal(["Value1", "Value2", "Value1", "Value2"], instance.StringArray1); Assert.Null(instance.StringArray2); - if (DisallowNullConfigSwitch) - { - Assert.Null(instance.StringArray3); // empty string should result in empty array - Assert.Null(instance.ByteArray2); // empty string should result in empty array - } - else - { - Assert.Empty(instance.StringArray3); // empty string should result in empty array - Assert.Empty(instance.ByteArray2); // empty string should result in empty array - } + 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 @@ -3078,16 +2990,8 @@ public void BindArraysWithNullAndOtherValues() inMemoryConfiguration.Bind(inMemoryInstance); Assert.Equal(["Value1", "Value2"], inMemoryInstance.StringArray1); Assert.Null(inMemoryInstance.StringArray2); - if (DisallowNullConfigSwitch) - { - Assert.Null(inMemoryInstance.StringArray3); // empty string should result in empty array - Assert.Null(inMemoryInstance.ByteArray2); // empty string should result in empty array - } - else - { - Assert.Empty(inMemoryInstance.StringArray3); // empty string should result in empty array - Assert.Empty(inMemoryInstance.ByteArray2); // empty string should result in empty array - } + 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); @@ -3096,16 +3000,8 @@ public void BindArraysWithNullAndOtherValues() inMemoryConfiguration.Bind(inMemoryInstance); Assert.Equal(["Value1", "Value2", "Value1", "Value2"], inMemoryInstance.StringArray1); Assert.Null(inMemoryInstance.StringArray2); - if (DisallowNullConfigSwitch) - { - Assert.Null(inMemoryInstance.StringArray3); // empty string should result in empty array - Assert.Null(inMemoryInstance.ByteArray2); // empty string should result in empty array - } - else - { - Assert.Empty(inMemoryInstance.StringArray3); // empty string should result in empty array - Assert.Empty(inMemoryInstance.ByteArray2); // empty string should result in empty array - } + 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 @@ -3115,96 +3011,6 @@ public void BindArraysWithNullAndOtherValues() #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 with the config switch on - // - - ProcessStartInfo psi = new ProcessStartInfo() { UseShellExecute = false }; - psi.Environment.Add("DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL", "true"); - - RemoteExecutor.Invoke((jsonConfiguration) => - { - var configuration1 = new ConfigurationBuilder() - .AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(jsonConfiguration))) - .Build().GetSection("ArraysContainer"); - - ArraysContainer instance1 = new(); // all properties are initialized to null. - configuration1.Bind(instance1); - - Assert.NotNull(instance1); - Assert.Equal(["Value1", "Value2"], instance1.StringArray1); - Assert.Null(instance1.StringArray2); - Assert.Null(instance1.StringArray3); - - Assert.Null(instance1.ByteArray1); - Assert.Null(instance1.ByteArray2); - Assert.Equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], instance1.ByteArray3); - - // Bind one more time and ensure the values are accumulated correctly. - configuration1.Bind(instance1); - Assert.Equal(["Value1", "Value2", "Value1", "Value2"], instance1.StringArray1); - Assert.Null(instance1.StringArray2); - Assert.Null(instance1.StringArray3); // empty string should result in empty array - - Assert.Null(instance1.ByteArray1); - Assert.Null(instance1.ByteArray2); // empty string should result in empty array -#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], instance1.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], instance1.ByteArray3); -#endif - // Test the same accumulation behavior with in-memory configuration - var inMemoryConfiguration1 = 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 inMemoryInstance1 = new(); - inMemoryConfiguration1.Bind(inMemoryInstance1); - Assert.Equal(["Value1", "Value2"], inMemoryInstance1.StringArray1); - Assert.Null(inMemoryInstance1.StringArray2); - Assert.Null(inMemoryInstance1.StringArray3); // empty string should result in empty array - Assert.Null(inMemoryInstance1.ByteArray2); // empty string should result in empty array - - Assert.Null(inMemoryInstance1.ByteArray1); - Assert.Equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], inMemoryInstance1.ByteArray3); - - // Bind one more time and ensure the values are accumulated correctly. - inMemoryConfiguration1.Bind(inMemoryInstance1); - Assert.Equal(["Value1", "Value2", "Value1", "Value2"], inMemoryInstance1.StringArray1); - Assert.Null(inMemoryInstance1.StringArray2); - Assert.Null(inMemoryInstance1.StringArray3); // empty string should result in empty array - Assert.Null(inMemoryInstance1.ByteArray2); // empty string should result in empty array - - Assert.Null(inMemoryInstance1.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], inMemoryInstance1.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], inMemoryInstance1.ByteArray3); -#endif - }, jsonConfig, new RemoteInvokeOptions { StartInfo = psi }).Dispose(); - } - - [Fact] - public void DisplayDisallowNullConfigSwitchValue() - { - _output.WriteLine($"DisallowNullConfigSwitch: {DisallowNullConfigSwitch}"); - Console.WriteLine($"DisallowNullConfigSwitch: {DisallowNullConfigSwitch}"); } } } 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 32e9ba1145d037..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 @@ -203,48 +203,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 719aa68f27539b..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 @@ -167,48 +167,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 94847e9eb6193d..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 @@ -167,48 +167,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 16e1dc12bb135d..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 @@ -167,48 +167,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 ff3799e1291993..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 @@ -59,48 +59,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } public static BinderOptions? GetBinderOptions(Action? configureOptions) { 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 ab449992c9d462..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 @@ -184,7 +184,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { instance.MyArray = instance.MyArray; } - if (!DisallowNullConfigSwitch && instance.MyArray is null && TryGetConfigurationValue(value9, key: null, out string? value13) && value13 == string.Empty) + if (instance.MyArray is null && TryGetConfigurationValue(value9, key: null, out string? value13) && value13 == string.Empty) { instance.MyArray = global::System.Array.Empty(); } @@ -225,48 +225,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 f414de9743d44b..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 @@ -88,48 +88,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } public static int ParseInt(string value, string? path) { 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 f24baafee55723..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 @@ -64,48 +64,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } public static int ParseInt(string value, string? path) { 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 ee431b4e8533e4..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 @@ -64,48 +64,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } public static int ParseInt(string value, string? path) { 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 d0d3037d4dd445..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 @@ -64,48 +64,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } public static bool ParseBool(string value, string? path) { 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 dab5ef6e4004c1..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 @@ -64,48 +64,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } public static global::System.Globalization.CultureInfo ParseSystemGlobalizationCultureInfo(string value, string? path) { 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 3523381ae4886c..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 @@ -127,48 +127,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } public static bool HasValueOrChildren(IConfiguration configuration) { 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 985317c13eaba7..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 @@ -165,7 +165,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { instance.MyArray = instance.MyArray; } - if (!DisallowNullConfigSwitch && instance.MyArray is null && TryGetConfigurationValue(value8, key: null, out string? value12) && value12 == string.Empty) + if (instance.MyArray is null && TryGetConfigurationValue(value8, key: null, out string? value12) && value12 == string.Empty) { instance.MyArray = global::System.Array.Empty(); } @@ -189,48 +189,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 1679172ae1e9f6..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 @@ -165,7 +165,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { instance.MyArray = instance.MyArray; } - if (!DisallowNullConfigSwitch && instance.MyArray is null && TryGetConfigurationValue(value8, key: null, out string? value12) && value12 == string.Empty) + if (instance.MyArray is null && TryGetConfigurationValue(value8, key: null, out string? value12) && value12 == string.Empty) { instance.MyArray = global::System.Array.Empty(); } @@ -189,48 +189,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 118fe52fa5933d..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 @@ -88,48 +88,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 ce5cba8fa2864c..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 @@ -88,48 +88,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 1cfc24851a3c2d..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 @@ -159,48 +159,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 b41fc3ff61cf75..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 @@ -159,48 +159,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 3d5c412490f966..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 @@ -165,48 +165,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 3c69c87c2d6485..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 @@ -159,48 +159,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 728e0aee54778b..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 @@ -221,48 +221,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 4e1ae4acb45486..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 @@ -221,48 +221,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 269a91865f6f36..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 @@ -221,48 +221,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 e3c1859c6e9523..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 @@ -215,48 +215,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 75841b437dc797..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 @@ -219,48 +219,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 0b1f9e2c6ee1e6..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 @@ -233,48 +233,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 b83ffde885b544..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 @@ -79,48 +79,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 16e27c71581733..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 @@ -379,7 +379,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } else { - if (!DisallowNullConfigSwitch && value23 == string.Empty) + if (value23 == string.Empty) { instance.Prop28 = ParseByteArray(value23, configuration.GetSection("Prop28").Path); } @@ -428,48 +428,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 af4840c0e743ee..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 @@ -180,7 +180,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { instance.Array = instance.Array; } - if (!DisallowNullConfigSwitch && instance.Array is null && TryGetConfigurationValue(value8, key: null, out string? value12) && value12 == string.Empty) + if (instance.Array is null && TryGetConfigurationValue(value8, key: null, out string? value12) && value12 == string.Empty) { instance.Array = global::System.Array.Empty(); } @@ -221,48 +221,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 26e2920525bc1c..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 @@ -194,48 +194,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 efb2b504b25ce1..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 @@ -164,48 +164,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 509c1e04721496..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 @@ -164,48 +164,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 e5f5505381b25f..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 @@ -164,48 +164,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 ff3799e1291993..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 @@ -59,48 +59,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } public static BinderOptions? GetBinderOptions(Action? configureOptions) { 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 c59f8ce08c3c2a..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 @@ -181,7 +181,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { instance.MyArray = instance.MyArray; } - if (!DisallowNullConfigSwitch && instance.MyArray is null && TryGetConfigurationValue(value9, key: null, out string? value13) && value13 == string.Empty) + if (instance.MyArray is null && TryGetConfigurationValue(value9, key: null, out string? value13) && value13 == string.Empty) { instance.MyArray = global::System.Array.Empty(); } @@ -222,48 +222,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 0a7995cb2323a1..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 @@ -87,48 +87,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } public static int ParseInt(string value, string? path) { 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 80ed7100ab82ce..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 @@ -61,48 +61,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } public static int ParseInt(string value, string? path) { 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 5335522e633c0c..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 @@ -62,48 +62,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } public static int ParseInt(string value, string? path) { 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 d9aff81b9f5ecb..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 @@ -61,48 +61,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } public static bool ParseBool(string value, string? path) { 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 d0b5cdb21004cf..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 @@ -62,48 +62,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } public static global::System.Globalization.CultureInfo ParseSystemGlobalizationCultureInfo(string value, string? path) { 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 255b4aa609d82f..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 @@ -124,48 +124,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } public static bool HasValueOrChildren(IConfiguration configuration) { 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 b5d4c253fde199..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 @@ -162,7 +162,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { instance.MyArray = instance.MyArray; } - if (!DisallowNullConfigSwitch && instance.MyArray is null && TryGetConfigurationValue(value8, key: null, out string? value12) && value12 == string.Empty) + if (instance.MyArray is null && TryGetConfigurationValue(value8, key: null, out string? value12) && value12 == string.Empty) { instance.MyArray = global::System.Array.Empty(); } @@ -186,48 +186,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 cddb35bbc0b5a7..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 @@ -162,7 +162,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { instance.MyArray = instance.MyArray; } - if (!DisallowNullConfigSwitch && instance.MyArray is null && TryGetConfigurationValue(value8, key: null, out string? value12) && value12 == string.Empty) + if (instance.MyArray is null && TryGetConfigurationValue(value8, key: null, out string? value12) && value12 == string.Empty) { instance.MyArray = global::System.Array.Empty(); } @@ -186,48 +186,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 47ed4e2ef9726d..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 @@ -85,48 +85,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 d2e297e59f2030..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 @@ -85,48 +85,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 9fb2ceff7cd86b..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 @@ -150,48 +150,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 08c998543d6fff..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 @@ -150,48 +150,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 bc1048a9b0a8ef..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 @@ -156,48 +156,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 913945f2f27013..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 @@ -150,48 +150,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 a7a90d9c91ba9d..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 @@ -215,48 +215,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 1d53fcb8148ec7..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 @@ -215,48 +215,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 305164fd0ff18d..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 @@ -215,48 +215,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 a32098d4d63242..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 @@ -209,48 +209,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 5293110c3f591e..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 @@ -216,48 +216,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 e0024bebe7297c..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 @@ -230,48 +230,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 6eb9f2860ce5df..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 @@ -76,48 +76,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 6ee203061bbdc0..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 @@ -436,7 +436,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } else { - if (!DisallowNullConfigSwitch && value28 == string.Empty) + if (value28 == string.Empty) { instance.Prop28 = ParseByteArray(value28, configuration.GetSection("Prop28").Path); } @@ -485,48 +485,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. 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 3547fc8bd3011e..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 @@ -174,7 +174,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { instance.Array = instance.Array; } - if (!DisallowNullConfigSwitch && instance.Array is null && TryGetConfigurationValue(value8, key: null, out string? value12) && value12 == string.Empty) + if (instance.Array is null && TryGetConfigurationValue(value8, key: null, out string? value12) && value12 == string.Empty) { instance.Array = global::System.Array.Empty(); } @@ -215,48 +215,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (configuration is ConfigurationSection section) { - var result = section.TryGetValue(key, out value); - if (!DisallowNullConfigSwitch) - { - return result; - } - } - else if (key != null) - { - value = configuration[key]; - } - else - { - value = configuration is IConfigurationSection sec ? sec.Value : null; + return section.TryGetValue(key, out value); } + value = key != null ? configuration[key] : configuration is IConfigurationSection sec ? sec.Value : null; return value != null; } - - /// Config switch to have the compatibility mode having the binder treat null configuration values as missing. - private static bool DisallowNullConfigSwitch { get; } = GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); - - private static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false) - { - if (global::System.AppContext.TryGetSwitch(switchName, out bool value)) - { - return value; - } - - if (global::System.Environment.GetEnvironmentVariable(envVariable) is string str) - { - if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return defaultValue; - } /// If required by the binder options, validates that there are no unknown keys in the input configuration object. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs index 41797c8fc52695..1a057d0e7b75f4 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs @@ -22,7 +22,7 @@ namespace Microsoft.Extensions.SourceGeneration.Configuration.Binder.Tests [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] public partial class ConfigurationBindingGeneratorTests : ConfigurationBinderTestsBase { - public ConfigurationBindingGeneratorTests(ITestOutputHelper output) : base(output) + public ConfigurationBindingGeneratorTests() : base() { } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests.csproj b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests.csproj index fb0ba94686233f..b6d959caeed4aa 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests.csproj @@ -26,7 +26,6 @@ - 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 c1aec5bfdd5442..fbdfb283a36030 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 @@ -13,7 +13,6 @@ Link="Common\ConfigurationProviderExtensions.cs" /> - diff --git a/src/libraries/Microsoft.Extensions.Configuration.Json/src/JsonConfigurationFileParser.cs b/src/libraries/Microsoft.Extensions.Configuration.Json/src/JsonConfigurationFileParser.cs index c3fd6b04a2a027..28e68f1607b148 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Json/src/JsonConfigurationFileParser.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Json/src/JsonConfigurationFileParser.cs @@ -15,7 +15,6 @@ private JsonConfigurationFileParser() { } private readonly Dictionary _data = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly Stack _paths = new Stack(); - private static bool DisallowNullConfigSwitch { get; } = AppContextSwitchHelper.GetBooleanConfig("Microsoft.Configuration.DisallowNull", "DOTNET_MICROSOFT_CONFIGURATION_DISALLOWNULL"); public static IDictionary Parse(Stream input) => new JsonConfigurationFileParser().ParseStream(input); @@ -83,7 +82,7 @@ private void SetEmptyIfElementIsEmpty(bool isEmpty) { if (isEmpty && _paths.Count > 0) { - _data[_paths.Peek()] = DisallowNullConfigSwitch ? null : string.Empty; + _data[_paths.Peek()] = string.Empty; } } @@ -111,7 +110,7 @@ private void VisitValue(JsonElement value) { throw new FormatException(SR.Format(SR.Error_KeyIsDuplicated, key)); } - _data[key] = !DisallowNullConfigSwitch && value.ValueKind == JsonValueKind.Null ? null : value.ToString(); + _data[key] = value.ValueKind == JsonValueKind.Null ? null : value.ToString(); break; default: diff --git a/src/libraries/Microsoft.Extensions.Configuration.Json/src/Microsoft.Extensions.Configuration.Json.csproj b/src/libraries/Microsoft.Extensions.Configuration.Json/src/Microsoft.Extensions.Configuration.Json.csproj index 1ce1ea83ba67af..1b1d2e7a165a4b 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Json/src/Microsoft.Extensions.Configuration.Json.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration.Json/src/Microsoft.Extensions.Configuration.Json.csproj @@ -8,10 +8,6 @@ JSON configuration provider implementation for Microsoft.Extensions.Configuration. This package enables you to read your application's settings from a JSON file. You can use JsonConfigurationExtensions.AddJsonFile extension method on IConfigurationBuilder to add the JSON configuration provider to the configuration builder. - - - - 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 dc6ce758d2b53c..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,7 +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 virtual bool TryGetValue(string? key, out string? value) { throw null; } + 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 eddbb15e8df8b7..4ebc121e0b31b2 100644 --- a/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationSection.cs +++ b/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationSection.cs @@ -66,7 +66,7 @@ public string? Value /// 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 virtual bool TryGetValue(string? key, out string? value) + public bool TryGetValue(string? key, out string? value) { string path = key is null ? Path : Path + ConfigurationPath.KeyDelimiter + key; if (_root.TryGetConfiguration(path, out value)) From aff3ead7670ebe913c72745aea638afe02c88e47 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Wed, 18 Jun 2025 10:28:48 -0700 Subject: [PATCH 3/4] Fix configuration providers failing tests --- .../tests/ConfigurationProviderCommandLineTest.cs | 2 ++ .../tests/ConfigurationProviderIniTest.cs | 2 ++ .../tests/ConfigurationProviderXmlTest.cs | 2 ++ .../tests/ConfigurationProviderTestBase.cs | 4 +++- 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/libraries/Microsoft.Extensions.Configuration.CommandLine/tests/ConfigurationProviderCommandLineTest.cs b/src/libraries/Microsoft.Extensions.Configuration.CommandLine/tests/ConfigurationProviderCommandLineTest.cs index 965cbc0377e271..a23683a8fed618 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.CommandLine/tests/ConfigurationProviderCommandLineTest.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.CommandLine/tests/ConfigurationProviderCommandLineTest.cs @@ -22,6 +22,8 @@ protected override (IConfigurationProvider Provider, Action Initializer) LoadThr return (provider, () => { }); } + 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.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/tests/ConfigurationProviderTestBase.cs b/src/libraries/Microsoft.Extensions.Configuration/tests/ConfigurationProviderTestBase.cs index 4658330ab45f36..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: null); + AssertConfig(BuildConfigRoot(LoadThroughProvider(TestSection.NullsTestConfig)), expectNulls: true, nullValue: SupportNullValues ? null : string.Empty); } [Fact] @@ -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) { From 98a809ea59a5b0dfb922dd2e402306d424c946ce Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Wed, 18 Jun 2025 12:15:02 -0700 Subject: [PATCH 4/4] Minor clean-up --- .../src/ConfigurationBinder.cs | 3 ++- .../tests/Common/ConfigurationBinderTests.Collections.cs | 5 ----- .../tests/Common/ConfigurationBinderTests.cs | 5 ----- .../SourceGenerationTests/ConfigBindingGenTestDriver.cs | 5 ----- ...nsions.Configuration.Binder.SourceGeneration.Tests.csproj | 1 - .../Microsoft.Extensions.Configuration.Binder.Tests.csproj | 1 - 6 files changed, 2 insertions(+), 18 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs index 9463b1c95396f8..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 || (propertyBindingPoint.HasNewValue))) + // 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); } 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 ef792c7c429375..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 @@ -10,7 +10,6 @@ using Microsoft.Extensions.Configuration; #endif using Xunit; -using Xunit.Abstractions; namespace Microsoft.Extensions #if BUILDING_SOURCE_GENERATOR_TESTS @@ -20,10 +19,6 @@ namespace Microsoft.Extensions { public sealed partial class ConfigurationBinderCollectionTests : ConfigurationBinderTestsBase { - public ConfigurationBinderCollectionTests() : base() - { - } - [Fact] public void GetList() { 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 fb72273b1c230d..a08ff66bd1e4d1 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs @@ -16,7 +16,6 @@ using Microsoft.Extensions.Configuration.Memory; using Microsoft.Extensions.Configuration.Test; using Xunit; -using Xunit.Abstractions; namespace Microsoft.Extensions #if BUILDING_SOURCE_GENERATOR_TESTS @@ -36,10 +35,6 @@ public ConfigurationBinderTestsBase() public partial class ConfigurationBinderTests : ConfigurationBinderTestsBase { - public ConfigurationBinderTests() : base() - { - } - [Fact] public void BindWithNestedTypesWithReadOnlyProperties() { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs index 1a057d0e7b75f4..b15815d72527fc 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs @@ -15,17 +15,12 @@ using Microsoft.Extensions.Configuration.Binder.SourceGeneration; using SourceGenerators.Tests; using Xunit; -using Xunit.Abstractions; namespace Microsoft.Extensions.SourceGeneration.Configuration.Binder.Tests { [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] public partial class ConfigurationBindingGeneratorTests : ConfigurationBinderTestsBase { - public ConfigurationBindingGeneratorTests() : base() - { - } - internal sealed class ConfigBindingGenTestDriver { private readonly CSharpParseOptions _parseOptions; diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests.csproj b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests.csproj index b6d959caeed4aa..ddc4d17d10f019 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests.csproj @@ -10,7 +10,6 @@ $(InterceptorsPreviewNamespaces);Microsoft.Extensions.Configuration.Binder.SourceGeneration true ..\..\src\Resources\Strings.resx - true 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 fbdfb283a36030..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 @@ -4,7 +4,6 @@ $(NetCoreAppCurrent);$(NetFrameworkCurrent) true ..\..\src\Resources\Strings.resx - true