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

Move ConfigurationBinder to Configuration #205

Closed
wants to merge 3 commits into from
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
255 changes: 255 additions & 0 deletions src/Microsoft.Framework.ConfigurationModel/ConfigurationBinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.Framework.Internal;

namespace Microsoft.Framework.ConfigurationModel
{
public static class ConfigurationBinder
{
public static TModel Bind<TModel>(IConfiguration configuration) where TModel : new()
{
var model = new TModel();
Bind(model, configuration);
return model;
}

public static void Bind(object model, IConfiguration configuration)
{
if (model == null)
{
return;
}

BindObjectProperties(model, configuration);
}

private static void BindObjectProperties(object obj, IConfiguration configuration)
{
foreach (var property in GetAllProperties(obj.GetType().GetTypeInfo()))
{
BindProperty(property, obj, configuration);
}
}

private static void BindProperty(PropertyInfo property, object propertyOwner, IConfiguration configuration)
{
configuration = configuration.GetConfigurationSection(property.Name);

if (property.GetMethod == null || !property.GetMethod.IsPublic)
{
// We don't support set only properties
return;
}

var propertyValue = property.GetValue(propertyOwner);
var hasPublicSetter = property.SetMethod != null && property.SetMethod.IsPublic;

if (propertyValue == null && !hasPublicSetter)
{
// Property doesn't have a value and we cannot set it so there is no
// point in going further down the graph
return;
}

propertyValue = BindType(
property.PropertyType,
propertyValue,
configuration);

if (propertyValue != null && hasPublicSetter)
{
property.SetValue(propertyOwner, propertyValue);
}
}

private static object BindType(Type type, object typeInstance, IConfiguration configuration)
{
var configValue = configuration.Get(null);
var typeInfo = type.GetTypeInfo();

if (configValue != null)
{
// Leaf nodes are always reinitialized
return CreateValueFromConfiguration(type, configValue, configuration);
}
else
{
var subkeys = configuration.GetConfigurationSections();
if (subkeys.Count() != 0)
{
if (typeInstance == null)
{
if (typeInfo.IsInterface || typeInfo.IsAbstract)
{
throw new InvalidOperationException(Resources.FormatError_CannotActivateAbstractOrInterface(type));
}

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

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

var collectionInterface = GetGenericOpenInterfaceImplementation(typeof(IDictionary<,>), type);
if (collectionInterface != null)
{
// Dictionary
BindDictionary(typeInstance, collectionInterface, configuration);
}
else
{
collectionInterface = GetGenericOpenInterfaceImplementation(typeof(ICollection<>), type);
if (collectionInterface != null)
{
// ICollection
BindCollection(typeInstance, collectionInterface, configuration);
}
else
{
// Something else
BindObjectProperties(typeInstance, configuration);
}
}
}
return typeInstance;
}
}

private static void BindDictionary(object dictionary, Type iDictionaryType, IConfiguration configuration)
{
var iDictionaryTypeInfo = iDictionaryType.GetTypeInfo();

// It is guaranteed to have a two and only two parameters
// because this is an IDictionary<K,V>
var keyType = iDictionaryTypeInfo.GenericTypeArguments[0];
var valueType = iDictionaryTypeInfo.GenericTypeArguments[1];

if (keyType != typeof(string))
{
// We only support string keys
return;
}

var addMethod = iDictionaryTypeInfo.GetDeclaredMethod("Add");
var subkeys = configuration.GetConfigurationSections().ToList();

foreach (var keyProperty in subkeys)
{
var keyConfiguration = keyProperty.Value;

var item = BindType(
type: valueType,
typeInstance: null,
configuration: keyConfiguration);
if (item != null)
{
addMethod.Invoke(dictionary, new[] { keyProperty.Key, item });
}
}
}

private static void BindCollection(object collection, Type iCollectionType, IConfiguration configuration)
{
var iCollectionTypeInfo = iCollectionType.GetTypeInfo();

// It is guaranteed to have a one and only one parameter
// because this is an ICollection<T>
var itemType = iCollectionTypeInfo.GenericTypeArguments[0];

var addMethod = iCollectionTypeInfo.GetDeclaredMethod("Add");
var subkeys = configuration.GetConfigurationSections().ToList();

foreach (var keyProperty in subkeys)
{
var keyConfiguration = keyProperty.Value;

try
{
var item = BindType(
type: itemType,
typeInstance: null,
configuration: keyConfiguration);
if (item != null)
{
addMethod.Invoke(collection, new[] { item });
}
}
catch
{
}
}
}

private static object CreateValueFromConfiguration(Type type, string value, IConfiguration configuration)
{
var typeInfo = type.GetTypeInfo();

if (typeInfo.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
return CreateValueFromConfiguration(Nullable.GetUnderlyingType(type), value, configuration);
}

var configurationValue = configuration.Get(key: null);

try
{
if (typeInfo.IsEnum)
{
return Enum.Parse(type, configurationValue);
}
else
{
return Convert.ChangeType(configurationValue, type);
}
}
catch (Exception ex)
{
throw new InvalidOperationException(Resources.FormatError_FailedBinding(configurationValue, type), ex);
}
}

private static Type GetGenericOpenInterfaceImplementation(Type expectedOpenGeneric, Type actual)
{
var interfaces = actual.GetTypeInfo().ImplementedInterfaces;
foreach (var interfaceType in interfaces)
{
if (interfaceType.GetTypeInfo().IsGenericType &&
interfaceType.GetGenericTypeDefinition() == expectedOpenGeneric)
{
return interfaceType;
}
}

return null;
}

private static IEnumerable<PropertyInfo> GetAllProperties(TypeInfo type)
{
var allProperties = new List<PropertyInfo>();

do
{
allProperties.AddRange(type.DeclaredProperties);
type = type.BaseType.GetTypeInfo();
}
while (type != typeof(object).GetTypeInfo());

return allProperties;
}
}
}

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

12 changes: 12 additions & 0 deletions src/Microsoft.Framework.ConfigurationModel/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,15 @@
<data name="Error_DuplicatedKeyInSwitchMappings" xml:space="preserve">
<value>Keys in switch mappings are case-insensitive. A duplicated key '{0}' was found.</value>
</data>
<data name="Error_CannotActivateAbstractOrInterface" xml:space="preserve">
<value>Cannot create instance of type '{0}' because it is either abstract or an interface.</value>
</data>
<data name="Error_FailedBinding" xml:space="preserve">
<value>Failed to convert '{0}' to type '{1}'.</value>
</data>
<data name="Error_FailedToActivate" xml:space="preserve">
<value>Failed to create instance of type '{0}'.</value>
</data>
<data name="Error_FileNotFound" xml:space="preserve">
<value>The configuration file '{0}' was not found and is not optional.</value>
</data>
Expand All @@ -141,6 +150,9 @@
<data name="Error_MissingBasePath" xml:space="preserve">
<value>Unable to resolve path '{0}'; construct this {1} with a non-null {2}.</value>
</data>
<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_NoCommitableSource" xml:space="preserve">
<value>No registered configuration source is capable of committing changes.</value>
</data>
Expand Down
Loading