Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix/66229 processing empty dictionary items #86394

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,24 @@ public BindingPoint(Func<object?> initialValueProvider, bool isReadOnly)

public bool IsReadOnly { get; }

public bool IsValueCanBeSet
{
get
{
return Value is null
&& !IsReadOnly;
}
}

public bool IsValueCanBeUpdated
{
get
{
return Value is not null
|| !IsReadOnly;
}
}

public bool HasNewValue
{
get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,129 +283,21 @@ private static void BindInstance(
if (type == typeof(IConfigurationSection))
{
bindingPoint.TrySetValue(config);
return;
}

var section = config as IConfigurationSection;
string? configValue = section?.Value;
if (configValue != null && TryConvertValue(type, configValue, section?.Path, out object? convertedValue, out Exception? error))
else
{
if (error != null)
{
throw error;
}

// Leaf nodes are always reinitialized
bindingPoint.TrySetValue(convertedValue);
return;
}

if (config != null && config.GetChildren().Any())
{
// for arrays and read-only list-like interfaces, we concatenate on to what is already there, if we can
if (type.IsArray || IsImmutableArrayCompatibleInterface(type))
{
if (!bindingPoint.IsReadOnly)
{
bindingPoint.SetValue(BindArray(type, (IEnumerable?)bindingPoint.Value, config, options));
}

// for getter-only collection properties that we can't add to, nothing more we can do
return;
}

// -----------------------------------------------------------------------------------------------------------------------------
// | bindingPoint | bindingPoint |
// Interface | Value | IsReadOnly | Behavior
// -----------------------------------------------------------------------------------------------------------------------------
// ISet<T> | not null | true/false | Use the Value instance to populate the configuration
// ISet<T> | null | false | Create HashSet<T> instance to populate the configuration
// ISet<T> | null | true | nothing
// IReadOnlySet<T> | null/not null | false | Create HashSet<T> instance, copy over existing values, and populate the configuration
// IReadOnlySet<T> | null/not null | true | nothing
// -----------------------------------------------------------------------------------------------------------------------------
if (TypeIsASetInterface(type))
if (TryBindAsSimpleValue(type, bindingPoint, config, out Exception? error))
{
if (!bindingPoint.IsReadOnly || bindingPoint.Value is not null)
if (error != null)
{
object? newValue = BindSet(type, (IEnumerable?)bindingPoint.Value, config, options);
if (!bindingPoint.IsReadOnly && newValue != null)
{
bindingPoint.SetValue(newValue);
}
throw error;
}

return;
}

// -----------------------------------------------------------------------------------------------------------------------------
// | bindingPoint | bindingPoint |
// Interface | Value | IsReadOnly | Behavior
// -----------------------------------------------------------------------------------------------------------------------------
// IDictionary<T> | not null | true/false | Use the Value instance to populate the configuration
// IDictionary<T> | null | false | Create Dictionary<T> instance to populate the configuration
// IDictionary<T> | null | true | nothing
// IReadOnlyDictionary<T> | null/not null | false | Create Dictionary<K,V> instance, copy over existing values, and populate the configuration
// IReadOnlyDictionary<T> | null/not null | true | nothing
// -----------------------------------------------------------------------------------------------------------------------------
if (TypeIsADictionaryInterface(type))
{
if (!bindingPoint.IsReadOnly || bindingPoint.Value is not null)
{
object? newValue = BindDictionaryInterface(bindingPoint.Value, type, config, options);
if (!bindingPoint.IsReadOnly && newValue != null)
{
bindingPoint.SetValue(newValue);
}
}

return;
}

// If we don't have an instance, try to create one
if (bindingPoint.Value is null)
{
// if the binding point doesn't let us set a new instance, there's nothing more we can do
if (bindingPoint.IsReadOnly)
{
return;
}

Type? interfaceGenericType = type.IsInterface && type.IsConstructedGenericType ? type.GetGenericTypeDefinition() : null;

if (interfaceGenericType is not null &&
(interfaceGenericType == typeof(ICollection<>) || interfaceGenericType == typeof(IList<>)))
{
// For ICollection<T> and IList<T> we bind them to mutable List<T> type.
Type genericType = typeof(List<>).MakeGenericType(type.GenericTypeArguments[0]);
bindingPoint.SetValue(Activator.CreateInstance(genericType));
}
else
{
bindingPoint.SetValue(CreateInstance(type, config, options));
}
}

Debug.Assert(bindingPoint.Value is not null);

// At this point we know that we have a non-null bindingPoint.Value, we just have to populate the items
// using the IDictionary<> or ICollection<> interfaces, or properties using reflection.
Type? dictionaryInterface = FindOpenGenericInterface(typeof(IDictionary<,>), type);

if (dictionaryInterface != null)
{
BindDictionary(bindingPoint.Value, dictionaryInterface, config, options);
}
else
{
Type? collectionInterface = FindOpenGenericInterface(typeof(ICollection<>), type);
if (collectionInterface != null)
{
BindCollection(bindingPoint.Value, collectionInterface, config, options);
}
else
if (!TryBindAsCollectionValue(type, bindingPoint, config, options))
{
BindProperties(bindingPoint.Value, config, options);
bindingPoint.SetValue(CreateInstance(type, config, options));
}
}
}
Expand Down Expand Up @@ -648,10 +540,7 @@ private static void BindDictionary(
bindingPoint: valueBindingPoint,
config: child,
options: options);
if (valueBindingPoint.HasNewValue)
{
indexerProperty.SetValue(dictionary, valueBindingPoint.Value, new object[] { key });
}
indexerProperty.SetValue(dictionary, valueBindingPoint.Value, new object[] { key });
}
catch (Exception ex)
{
Expand Down Expand Up @@ -829,6 +718,160 @@ private static Array BindArray(Type type, IEnumerable? source, IConfiguration co
return source;
}

[RequiresDynamicCode(DynamicCodeWarningMessage)]
[RequiresUnreferencedCode(TrimmingWarningMessage)]
private static bool TryBindAsCollectionValue(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type type,
BindingPoint bindingPoint,
IConfiguration config,
BinderOptions options)
{
if (config != null && config.GetChildren().Any())
{
// for arrays and read-only list-like interfaces, we concatenate on to what is already there, if we can
if (type.IsArray || IsImmutableArrayCompatibleInterface(type))
{
if (!bindingPoint.IsReadOnly)
{
bindingPoint.SetValue(BindArray(type, (IEnumerable?)bindingPoint.Value, config, options));
}

// for getter-only collection properties that we can't add to, nothing more we can do
return true;
}

// -----------------------------------------------------------------------------------------------------------------------------
// | bindingPoint | bindingPoint |
// Interface | Value | IsReadOnly | Behavior
// -----------------------------------------------------------------------------------------------------------------------------
// ISet<T> | not null | true/false | Use the Value instance to populate the configuration
// ISet<T> | null | false | Create HashSet<T> instance to populate the configuration
// ISet<T> | null | true | nothing
// IReadOnlySet<T> | null/not null | false | Create HashSet<T> instance, copy over existing values, and populate the configuration
// IReadOnlySet<T> | null/not null | true | nothing
// -----------------------------------------------------------------------------------------------------------------------------
if (TypeIsASetInterface(type))
{
if (bindingPoint.IsValueCanBeUpdated)
{
object? newValue = BindSet(type, (IEnumerable?)bindingPoint.Value, config, options);
if (!bindingPoint.IsReadOnly && newValue != null)
{
bindingPoint.SetValue(newValue);
}
}

return true;
}

// -----------------------------------------------------------------------------------------------------------------------------
// | bindingPoint | bindingPoint |
// Interface | Value | IsReadOnly | Behavior
// -----------------------------------------------------------------------------------------------------------------------------
// IDictionary<T> | not null | true/false | Use the Value instance to populate the configuration
// IDictionary<T> | null | false | Create Dictionary<T> instance to populate the configuration
// IDictionary<T> | null | true | nothing
// IReadOnlyDictionary<T> | null/not null | false | Create Dictionary<K,V> instance, copy over existing values, and populate the configuration
// IReadOnlyDictionary<T> | null/not null | true | nothing
// -----------------------------------------------------------------------------------------------------------------------------
if (TypeIsADictionaryInterface(type))
{
if (bindingPoint.IsValueCanBeUpdated)
{
object? newValue = BindDictionaryInterface(bindingPoint.Value, type, config, options);
if (!bindingPoint.IsReadOnly && newValue != null)
{
bindingPoint.SetValue(newValue);
}
}

return true;
}

if (bindingPoint.IsValueCanBeSet)
{
Type? interfaceGenericType = type.IsInterface && type.IsConstructedGenericType ? type.GetGenericTypeDefinition() : null;

if (interfaceGenericType is not null &&
(interfaceGenericType == typeof(ICollection<>) || interfaceGenericType == typeof(IList<>)))
{
// For ICollection<T> and IList<T> we bind them to mutable List<T> type.
Type genericType = typeof(List<>).MakeGenericType(type.GenericTypeArguments[0]);
bindingPoint.SetValue(Activator.CreateInstance(genericType));
}
else
{
bindingPoint.SetValue(CreateInstance(type, config, options));
}
}

Debug.Assert(bindingPoint.Value is not null);

// At this point we know that we have a non-null bindingPoint.Value, we just have to populate the items
// using the IDictionary<> or ICollection<> interfaces, or properties using reflection.
Type? dictionaryInterface = FindOpenGenericInterface(typeof(IDictionary<,>), type);

if (dictionaryInterface != null)
{
BindDictionary(bindingPoint.Value, dictionaryInterface, config, options);
}
else
{
Type? collectionInterface = FindOpenGenericInterface(typeof(ICollection<>), type);
if (collectionInterface != null)
{
BindCollection(bindingPoint.Value, collectionInterface, config, options);
}
else
{
BindProperties(bindingPoint.Value, config, options);
}
}

return true;
}
else
{
return false;
}
}

[RequiresUnreferencedCode(TrimmingWarningMessage)]
private static bool TryBindAsSimpleValue(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type type,
BindingPoint bindingPoint,
IConfiguration config,
out Exception? error)
{
bool returnValue;

var section = config as IConfigurationSection;
string? configValue = section?.Value;

if (configValue != null)
{
if (TryConvertValue(type, configValue, section?.Path, out object? convertedValue, out error))
{
if (error == null)
{
bindingPoint.TrySetValue(convertedValue);
}
returnValue = true;
}
else
{
returnValue = false;
}
}
else
{
error = null;
returnValue = false;
}

return returnValue;
}

[RequiresUnreferencedCode(TrimmingWarningMessage)]
private static bool TryConvertValue(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Configuration;
using Xunit;

Expand Down Expand Up @@ -580,6 +581,30 @@ public class ClassWithIndirectSelfReference
public List<ClassWithIndirectSelfReference> MyList { get; set; }
}

public class DistributedQueueConfig
{
[JsonPropertyName("namespaces")]
public IList<QueueNamespaces> Namespaces { get; set; }
}

public class QueueNamespaces
{
[JsonPropertyName("namespace")]
public string Namespace { get; set; }

[JsonPropertyName("queues")]
public Dictionary<string, QueueProperties> Queues { get; set; } = new();
}

public class QueueProperties
{
[JsonPropertyName("creationDate")]
public DateTimeOffset? CreationDate { get; set; }

[JsonPropertyName("dequeueOnlyMarkedDate")]
public DateTimeOffset? DequeueOnlyMarkedDate { get; set; } = default(DateTimeOffset);
}

public record RecordWithPrimitives
{
public bool Prop0 { get; set; }
Expand Down
Loading