Skip to content
This repository has been archived by the owner on Dec 18, 2018. It is now read-only.

Commit

Permalink
Adding support for arrays to ConfigurationBinder
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaCo authored and HaoK committed Aug 26, 2015
1 parent 547a8fa commit 3125ec9
Show file tree
Hide file tree
Showing 4 changed files with 269 additions and 42 deletions.
137 changes: 95 additions & 42 deletions src/Microsoft.Framework.Configuration.Binder/ConfigurationBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ public static void Bind(this IConfiguration configuration, object model)
private static void BindProperty(PropertyInfo property, object instance, IConfiguration config)
{
// We don't support set only, non public, or indexer properties
if (property.GetMethod == null ||
!property.GetMethod.IsPublic ||
if (property.GetMethod == null ||
!property.GetMethod.IsPublic ||
property.GetMethod.GetParameters().Length > 0)
{
return;
Expand Down Expand Up @@ -59,56 +59,75 @@ private static object BindInstance(Type type, object instance, IConfiguration co
// Leaf nodes are always reinitialized
return ReadValue(type, configValue, section);
}
else

if (config.GetChildren().Any())
{
if (config.GetChildren().Count() != 0)
if (instance == null)
{
if (instance == null)
{
var typeInfo = type.GetTypeInfo();
if (typeInfo.IsInterface || typeInfo.IsAbstract)
{
throw new InvalidOperationException(Resources.FormatError_CannotActivateAbstractOrInterface(type));
}

var hasDefaultConstructor = typeInfo.DeclaredConstructors.Any(ctor => ctor.IsPublic && ctor.GetParameters().Length == 0);
if (!hasDefaultConstructor)
{
throw new InvalidOperationException(Resources.FormatError_MissingParameterlessConstructor(type));
}

try
{
instance = Activator.CreateInstance(type);
}
catch (Exception ex)
{
throw new InvalidOperationException(Resources.FormatError_FailedToActivate(type), ex);
}
}
instance = CreateInstance(type);
}

// See if its a Dictionary
var collectionInterface = FindOpenGenericInterface(typeof(IDictionary<,>), type);
// See if its a Dictionary
var collectionInterface = FindOpenGenericInterface(typeof(IDictionary<,>), type);
if (collectionInterface != null)
{
BindDictionary(instance, collectionInterface, config);
}
else if (type.IsArray)
{
instance = BindArray((Array)instance, config);
}
else
{
// See if its an ICollection
collectionInterface = FindOpenGenericInterface(typeof(ICollection<>), type);
if (collectionInterface != null)
{
BindDictionary(instance, collectionInterface, config);
BindCollection(instance, collectionInterface, config);
}
// Something else
else
{
// See if its an ICollection
collectionInterface = FindOpenGenericInterface(typeof(ICollection<>), type);
if (collectionInterface != null)
{
BindCollection(instance, collectionInterface, config);
}
// Something else
else
{
Bind(config, instance);
}
Bind(config, instance);
}
}
return instance;
}

return instance;
}

private static object CreateInstance(Type type)
{
var typeInfo = type.GetTypeInfo();

if (typeInfo.IsInterface || typeInfo.IsAbstract)
{
throw new InvalidOperationException(Resources.FormatError_CannotActivateAbstractOrInterface(type));
}

if (typeInfo.IsArray)
{
if (typeInfo.GetArrayRank() > 1)
{
throw new InvalidOperationException(Resources.FormatError_UnsupportedMultidimensionalArray(type));
}

return Array.CreateInstance(typeInfo.GetElementType(), 0);
}

var hasDefaultConstructor = typeInfo.DeclaredConstructors.Any(ctor => ctor.IsPublic && ctor.GetParameters().Length == 0);
if (!hasDefaultConstructor)
{
throw new InvalidOperationException(Resources.FormatError_MissingParameterlessConstructor(type));
}

try
{
return Activator.CreateInstance(type);
}
catch (Exception ex)
{
throw new InvalidOperationException(Resources.FormatError_FailedToActivate(type), ex);
}
}

Expand Down Expand Up @@ -175,6 +194,40 @@ private static void BindCollection(object collection, Type collectionType, IConf
}
}

private static Array BindArray(Array source, IConfiguration config)
{
var children = config.GetChildren().ToArray();
var arrayLength = source.Length;
var elementType = source.GetType().GetElementType();
var newArray = Array.CreateInstance(elementType, arrayLength + children.Length);

// binding to array has to preserve already initialized arrays with values
if (arrayLength > 0)
{
Array.Copy(source, newArray, arrayLength);
}

for(int i = 0; i < children.Length; i++)
{
try
{
var item = BindInstance(
type: elementType,
instance: null,
config: children[i]);
if (item != null)
{
newArray.SetValue(item, arrayLength + i);
}
}
catch
{
}
}

return newArray;
}

private static object ReadValue(Type type, string value, IConfigurationSection config)
{
if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/Microsoft.Framework.Configuration.Binder/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,7 @@
<data name="Error_MissingParameterlessConstructor" xml:space="preserve">
<value>Cannot create instance of type '{0}' because it is missing a public parameterless constructor.</value>
</data>
<data name="Error_UnsupportedMultidimensionalArray" xml:space="preserve">
<value>Cannot create instance of type '{0}' because multidimensional arrays are not supported.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,138 @@ public void NonStringKeyDictionaryBinding()
Assert.Equal(0, options.NonStringKeyDictionary.Count);
}

[Fact]
public void StringArrayBinding()
{
var input = new Dictionary<string, string>
{
{"StringArray:0", "val0"},
{"StringArray:1", "val1"},
{"StringArray:2", "val2"},
{"StringArray:x", "valx"}
};

var builder = new ConfigurationBuilder(new MemoryConfigurationSource(input));
var config = builder.Build();
var options = new OptionsWithArrays();
config.Bind(options);

var array = options.StringArray;

Assert.Equal(4, array.Length);

Assert.Equal("val0", array[0]);
Assert.Equal("val1", array[1]);
Assert.Equal("val2", array[2]);
Assert.Equal("valx", array[3]);
}

[Fact]
public void AlreadyInitializedArrayBinding()
{
var input = new Dictionary<string, string>
{
{"AlreadyInitializedArray:0", "val0"},
{"AlreadyInitializedArray:1", "val1"},
{"AlreadyInitializedArray:2", "val2"},
{"AlreadyInitializedArray:x", "valx"}
};

var builder = new ConfigurationBuilder(new MemoryConfigurationSource(input));
var config = builder.Build();

var options = new OptionsWithArrays();
config.Bind(options);

var array = options.AlreadyInitializedArray;

Assert.Equal(7, array.Length);

Assert.Equal(OptionsWithArrays.InitialValue, array[0]);
Assert.Equal(null, array[1]);
Assert.Equal(null, array[2]);
Assert.Equal("val0", array[3]);
Assert.Equal("val1", array[4]);
Assert.Equal("val2", array[5]);
Assert.Equal("valx", array[6]);
}

[Fact]
public void ArrayInNestedOptionBinding()
{
var input = new Dictionary<string, string>
{
{"ObjectArray:0:ArrayInNestedOption:0", "0"},
{"ObjectArray:0:ArrayInNestedOption:1", "1"},
{"ObjectArray:1:ArrayInNestedOption:0", "10"},
{"ObjectArray:1:ArrayInNestedOption:1", "11"},
{"ObjectArray:1:ArrayInNestedOption:2", "12"},
};

var builder = new ConfigurationBuilder(new MemoryConfigurationSource(input));
var config = builder.Build();
var options = new OptionsWithArrays();
config.Bind(options);

Assert.Equal(2, options.ObjectArray.Length);
Assert.Equal(2, options.ObjectArray[0].ArrayInNestedOption.Length);
Assert.Equal(3, options.ObjectArray[1].ArrayInNestedOption.Length);

Assert.Equal(0, options.ObjectArray[0].ArrayInNestedOption[0]);
Assert.Equal(1, options.ObjectArray[0].ArrayInNestedOption[1]);
Assert.Equal(10, options.ObjectArray[1].ArrayInNestedOption[0]);
Assert.Equal(11, options.ObjectArray[1].ArrayInNestedOption[1]);
Assert.Equal(12, options.ObjectArray[1].ArrayInNestedOption[2]);
}

[Fact]
public void UnsupportedMultidimensionalArrays()
{
var input = new Dictionary<string, string>
{
{"DimensionalArray:0:0", "a"},
{"DimensionalArray:0:1", "b"}
};

var builder = new ConfigurationBuilder(new MemoryConfigurationSource(input));
var config = builder.Build();
var options = new OptionsWithArrays();

var exception = Assert.Throws<InvalidOperationException>(
() => config.Bind(options));
Assert.Equal(
Resources.FormatError_UnsupportedMultidimensionalArray(typeof(string[,])),
exception.Message);
}

[Fact]
public void JaggedArrayBinding()
{
var input = new Dictionary<string, string>
{
{"JaggedArray:0:0", "00"},
{"JaggedArray:0:1", "01"},
{"JaggedArray:1:0", "10"},
{"JaggedArray:1:1", "11"},
{"JaggedArray:1:2", "12"},
};

var builder = new ConfigurationBuilder(new MemoryConfigurationSource(input));
var config = builder.Build();
var options = new OptionsWithArrays();
config.Bind(options);

Assert.Equal(2, options.JaggedArray.Length);
Assert.Equal(2, options.JaggedArray[0].Length);
Assert.Equal(3, options.JaggedArray[1].Length);

Assert.Equal("00", options.JaggedArray[0][0]);
Assert.Equal("01", options.JaggedArray[0][1]);
Assert.Equal("10", options.JaggedArray[1][0]);
Assert.Equal("11", options.JaggedArray[1][1]);
Assert.Equal("12", options.JaggedArray[1][2]);
}

private class CustomList : List<string>
{
// Add an overload, just to make sure binding picks the right Add method
Expand All @@ -326,6 +458,29 @@ private class NestedOptions
public int Integer { get; set; }

public List<string> ListInNestedOption { get; set; }

public int[] ArrayInNestedOption { get; set; }
}

private class OptionsWithArrays
{
public const string InitialValue = "This was here before";

public OptionsWithArrays()
{
AlreadyInitializedArray = new string[] { InitialValue, null, null };
}

public string[] AlreadyInitializedArray { get; set; }

public string[] StringArray { get; set; }

// this should throw becase we do not support multidimensional arrays
public string[,] DimensionalArray { get; set; }

public string[][] JaggedArray { get; set; }

public NestedOptions[] ObjectArray { get; set; }
}

private class OptionsWithLists
Expand Down

1 comment on commit 3125ec9

@Fosol
Copy link

@Fosol Fosol commented on 3125ec9 Aug 27, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome!

Please sign in to comment.