未验证 提交 048da75f 编写于 作者: S Steve Dunn 提交者: GitHub

Fixes Binding to non-null IEnumerable doesn't work #36390 (#66131)

上级 c032e0d8
......@@ -2,8 +2,10 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
......@@ -378,10 +380,19 @@ private static void BindProperty(PropertyInfo property, object instance, IConfig
{
BindCollection(instance, collectionInterface, config, options);
}
// Something else
else
{
BindNonScalar(config, instance, options);
// See if its an IEnumerable
collectionInterface = FindOpenGenericInterface(typeof(IEnumerable<>), type);
if (collectionInterface != null)
{
instance = BindExistingCollection((IEnumerable)instance!, config, options);
}
// Something else
else
{
BindNonScalar(config, instance, options);
}
}
}
}
......@@ -498,6 +509,41 @@ private static void BindProperty(PropertyInfo property, object instance, IConfig
}
}
[RequiresUnreferencedCode("Cannot statically analyze what the element type is of the object collection so its members may be trimmed.")]
private static IEnumerable BindExistingCollection(IEnumerable source, IConfiguration config, BinderOptions options)
{
// find the interface that is IEnumerable<T>
Type type = source.GetType().GetInterface("IEnumerable`1", false)!;
Type elementType = type.GenericTypeArguments[0];
Type genericType = typeof(List<>).MakeGenericType(elementType);
IList newList = (IList)Activator.CreateInstance(genericType, source)!;
IConfigurationSection[] children = config.GetChildren().ToArray();
for (int i = 0; i < children.Length; i++)
{
try
{
object? item = BindInstance(
type: elementType,
instance: null,
config: children[i],
options: options);
if (item != null)
{
newList.Add(item);
}
}
catch
{
}
}
return newList;
}
[RequiresUnreferencedCode("Cannot statically analyze what the element type is of the Array so its members may be trimmed.")]
private static Array BindArray(Array source, IConfiguration config, BinderOptions options)
{
......
......@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using Xunit;
......@@ -47,6 +48,11 @@ public string ReadOnly
{
get { return null; }
}
public IEnumerable<string> NonInstantiatedIEnumerable { get; set; } = null!;
public IEnumerable<string> InstantiatedIEnumerable { get; set; } = new List<string>();
public ICollection<string> InstantiatedICollection { get; set; } = new List<string>();
public IReadOnlyCollection<string> InstantiatedIReadOnlyCollection { get; set; } = new List<string>();
}
public class NestedOptions
......@@ -220,6 +226,107 @@ public void CanBindConfigurationKeyNameAttributes()
Assert.Equal("Yo", options.NamedProperty);
}
[Fact]
public void CanBindNonInstantiatedIEnumerableWithItems()
{
var dic = new Dictionary<string, string>
{
{"NonInstantiatedIEnumerable:0", "Yo1"},
{"NonInstantiatedIEnumerable:1", "Yo2"},
};
var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddInMemoryCollection(dic);
var config = configurationBuilder.Build();
var options = config.Get<ComplexOptions>()!;
Assert.Equal(2, options.NonInstantiatedIEnumerable.Count());
Assert.Equal("Yo1", options.NonInstantiatedIEnumerable.ElementAt(0));
Assert.Equal("Yo2", options.NonInstantiatedIEnumerable.ElementAt(1));
}
[Fact]
public void CanBindInstantiatedIEnumerableWithItems()
{
var dic = new Dictionary<string, string>
{
{"InstantiatedIEnumerable:0", "Yo1"},
{"InstantiatedIEnumerable:1", "Yo2"},
};
var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddInMemoryCollection(dic);
var config = configurationBuilder.Build();
var options = config.Get<ComplexOptions>()!;
Assert.Equal(2, options.InstantiatedIEnumerable.Count());
Assert.Equal("Yo1", options.InstantiatedIEnumerable.ElementAt(0));
Assert.Equal("Yo2", options.InstantiatedIEnumerable.ElementAt(1));
}
[Fact]
public void CanBindInstantiatedICollectionWithItems()
{
var dic = new Dictionary<string, string>
{
{"InstantiatedICollection:0", "Yo1"},
{"InstantiatedICollection:1", "Yo2"},
};
var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddInMemoryCollection(dic);
var config = configurationBuilder.Build();
var options = config.Get<ComplexOptions>()!;
Assert.Equal(2, options.InstantiatedICollection.Count());
Assert.Equal("Yo1", options.InstantiatedICollection.ElementAt(0));
Assert.Equal("Yo2", options.InstantiatedICollection.ElementAt(1));
}
[Fact]
public void CanBindInstantiatedIReadOnlyCollectionWithItems()
{
var dic = new Dictionary<string, string>
{
{"InstantiatedIReadOnlyCollection:0", "Yo1"},
{"InstantiatedIReadOnlyCollection:1", "Yo2"},
};
var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddInMemoryCollection(dic);
var config = configurationBuilder.Build();
var options = config.Get<ComplexOptions>()!;
Assert.Equal(2, options.InstantiatedIReadOnlyCollection.Count);
Assert.Equal("Yo1", options.InstantiatedIReadOnlyCollection.ElementAt(0));
Assert.Equal("Yo2", options.InstantiatedIReadOnlyCollection.ElementAt(1));
}
[Fact]
public void CanBindInstantiatedIEnumerableWithNullItems()
{
var dic = new Dictionary<string, string>
{
{"InstantiatedIEnumerable:0", null},
{"InstantiatedIEnumerable:1", "Yo1"},
{"InstantiatedIEnumerable:2", "Yo2"},
};
var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddInMemoryCollection(dic);
var config = configurationBuilder.Build();
var options = config.Get<ComplexOptions>()!;
Assert.Equal(2, options.InstantiatedIEnumerable.Count());
Assert.Equal("Yo1", options.InstantiatedIEnumerable.ElementAt(0));
Assert.Equal("Yo2", options.InstantiatedIEnumerable.ElementAt(1));
}
[Fact]
public void EmptyStringIsNullable()
{
......
......@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Xunit;
......@@ -958,6 +959,101 @@ public void CanBindUninitializedIEnumerable()
Assert.Equal("valx", array[3]);
}
[Fact]
public void CanBindInitializedIEnumerableAndTheOriginalItemsAreNotMutated()
{
var input = new Dictionary<string, string>
{
{"AlreadyInitializedIEnumerableInterface:0", "val0"},
{"AlreadyInitializedIEnumerableInterface:1", "val1"},
{"AlreadyInitializedIEnumerableInterface:2", "val2"},
{"AlreadyInitializedIEnumerableInterface:x", "valx"}
};
var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddInMemoryCollection(input);
var config = configurationBuilder.Build();
var options = new InitializedCollectionsOptions();
config.Bind(options);
var array = options.AlreadyInitializedIEnumerableInterface.ToArray();
Assert.Equal(6, array.Length);
Assert.Equal("This was here too", array[0]);
Assert.Equal("Don't touch me!", array[1]);
Assert.Equal("val0", array[2]);
Assert.Equal("val1", array[3]);
Assert.Equal("val2", array[4]);
Assert.Equal("valx", array[5]);
// the original list hasn't been touched
Assert.Equal(2, options.ListUsedInIEnumerableFieldAndShouldNotBeTouched.Count);
Assert.Equal("This was here too", options.ListUsedInIEnumerableFieldAndShouldNotBeTouched.ElementAt(0));
Assert.Equal("Don't touch me!", options.ListUsedInIEnumerableFieldAndShouldNotBeTouched.ElementAt(1));
}
[Fact]
public void CanBindInitializedCustomIEnumerableBasedList()
{
// A field declared as IEnumerable<T> that is instantiated with a class
// that directly implements IEnumerable<T> is still bound, but with
// a new List<T> with the original values copied over.
var input = new Dictionary<string, string>
{
{"AlreadyInitializedCustomListDerivedFromIEnumerable:0", "val0"},
{"AlreadyInitializedCustomListDerivedFromIEnumerable:1", "val1"},
};
var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddInMemoryCollection(input);
var config = configurationBuilder.Build();
var options = new InitializedCollectionsOptions();
config.Bind(options);
var array = options.AlreadyInitializedCustomListDerivedFromIEnumerable.ToArray();
Assert.Equal(4, array.Length);
Assert.Equal("Item1", array[0]);
Assert.Equal("Item2", array[1]);
Assert.Equal("val0", array[2]);
Assert.Equal("val1", array[3]);
}
[Fact]
public void CanBindInitializedCustomIndirectlyDerivedIEnumerableList()
{
// A field declared as IEnumerable<T> that is instantiated with a class
// that indirectly implements IEnumerable<T> is still bound, but with
// a new List<T> with the original values copied over.
var input = new Dictionary<string, string>
{
{"AlreadyInitializedCustomListIndirectlyDerivedFromIEnumerable:0", "val0"},
{"AlreadyInitializedCustomListIndirectlyDerivedFromIEnumerable:1", "val1"},
};
var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddInMemoryCollection(input);
var config = configurationBuilder.Build();
var options = new InitializedCollectionsOptions();
config.Bind(options);
var array = options.AlreadyInitializedCustomListIndirectlyDerivedFromIEnumerable.ToArray();
Assert.Equal(4, array.Length);
Assert.Equal("Item1", array[0]);
Assert.Equal("Item2", array[1]);
Assert.Equal("val0", array[2]);
Assert.Equal("val1", array[3]);
}
[Fact]
public void CanBindUninitializedICollection()
{
......@@ -1101,6 +1197,28 @@ private class UnintializedCollectionsOptions
public IReadOnlyDictionary<string, string> IReadOnlyDictionary { get; set; }
}
private class InitializedCollectionsOptions
{
public InitializedCollectionsOptions()
{
AlreadyInitializedIEnumerableInterface = ListUsedInIEnumerableFieldAndShouldNotBeTouched;
}
public List<string> ListUsedInIEnumerableFieldAndShouldNotBeTouched = new List<string>
{
"This was here too",
"Don't touch me!"
};
public IEnumerable<string> AlreadyInitializedIEnumerableInterface { get; set; }
public IEnumerable<string> AlreadyInitializedCustomListDerivedFromIEnumerable { get; set; } =
new CustomListDerivedFromIEnumerable();
public IEnumerable<string> AlreadyInitializedCustomListIndirectlyDerivedFromIEnumerable { get; set; } =
new CustomListIndirectlyDerivedFromIEnumerable();
}
private class CustomList : List<string>
{
// Add an overload, just to make sure binding picks the right Add method
......@@ -1109,6 +1227,32 @@ public void Add(string a, string b)
}
}
private class CustomListDerivedFromIEnumerable : IEnumerable<string>
{
private readonly List<string> _items = new List<string> { "Item1", "Item2" };
public IEnumerator<string> GetEnumerator() => _items.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
internal interface IDerivedOne : IDerivedTwo
{
}
internal interface IDerivedTwo : IEnumerable<string>
{
}
private class CustomListIndirectlyDerivedFromIEnumerable : IDerivedOne
{
private readonly List<string> _items = new List<string> { "Item1", "Item2" };
public IEnumerator<string> GetEnumerator() => _items.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
private class CustomDictionary<T> : Dictionary<string, T>
{
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册