717 lines
34 KiB
C#
717 lines
34 KiB
C#
// 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;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Linq.Expressions;
|
|
using System.Reflection;
|
|
using System.Runtime.ExceptionServices;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.AspNet.Http;
|
|
using Microsoft.AspNet.Mvc.Core;
|
|
using Microsoft.AspNet.Mvc.Formatters;
|
|
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
|
|
using Microsoft.Framework.Internal;
|
|
|
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
|
{
|
|
public static class ModelBindingHelper
|
|
{
|
|
/// <summary>
|
|
/// Updates the specified <paramref name="model"/> instance using the specified <paramref name="modelBinder"/>
|
|
/// and the specified <paramref name="valueProvider"/> and executes validation using the specified
|
|
/// <paramref name="validatorProvider"/>.
|
|
/// </summary>
|
|
/// <typeparam name="TModel">The type of the model object.</typeparam>
|
|
/// <param name="model">The model instance to update and validate.</param>
|
|
/// <param name="prefix">The prefix to use when looking up values in the <paramref name="valueProvider"/>.
|
|
/// </param>
|
|
/// <param name="httpContext">The <see cref="HttpContext"/> for the current executing request.</param>
|
|
/// <param name="modelState">The <see cref="ModelStateDictionary"/> used for maintaining state and
|
|
/// results of model-binding validation.</param>
|
|
/// <param name="metadataProvider">The provider used for reading metadata for the model type.</param>
|
|
/// <param name="modelBinder">The <see cref="IModelBinder"/> used for binding.</param>
|
|
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
|
|
/// <param name="inputFormatters">
|
|
/// The set of <see cref="IInputFormatter"/> instances for deserializing the body.
|
|
/// </param>
|
|
/// <param name="objectModelValidator">The <see cref="IObjectModelValidator"/> used for validating the
|
|
/// bound values.</param>
|
|
/// <param name="validatorProvider">The <see cref="IModelValidatorProvider"/> used for executing validation
|
|
/// on the model instance.</param>
|
|
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful</returns>
|
|
public static Task<bool> TryUpdateModelAsync<TModel>(
|
|
[NotNull] TModel model,
|
|
[NotNull] string prefix,
|
|
[NotNull] HttpContext httpContext,
|
|
[NotNull] ModelStateDictionary modelState,
|
|
[NotNull] IModelMetadataProvider metadataProvider,
|
|
[NotNull] IModelBinder modelBinder,
|
|
[NotNull] IValueProvider valueProvider,
|
|
[NotNull] IList<IInputFormatter> inputFormatters,
|
|
[NotNull] IObjectModelValidator objectModelValidator,
|
|
[NotNull] IModelValidatorProvider validatorProvider)
|
|
where TModel : class
|
|
{
|
|
// Includes everything by default.
|
|
return TryUpdateModelAsync(
|
|
model,
|
|
prefix,
|
|
httpContext,
|
|
modelState,
|
|
metadataProvider,
|
|
modelBinder,
|
|
valueProvider,
|
|
inputFormatters,
|
|
objectModelValidator,
|
|
validatorProvider,
|
|
predicate: (context, propertyName) => true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the specified <paramref name="model"/> instance using the specified <paramref name="modelBinder"/>
|
|
/// and the specified <paramref name="valueProvider"/> and executes validation using the specified
|
|
/// <paramref name="validatorProvider"/>.
|
|
/// </summary>
|
|
/// <typeparam name="TModel">The type of the model object.</typeparam>
|
|
/// <param name="model">The model instance to update and validate.</param>
|
|
/// <param name="prefix">The prefix to use when looking up values in the <paramref name="valueProvider"/>.
|
|
/// </param>
|
|
/// <param name="httpContext">The <see cref="HttpContext"/> for the current executing request.</param>
|
|
/// <param name="modelState">The <see cref="ModelStateDictionary"/> used for maintaining state and
|
|
/// results of model-binding validation.</param>
|
|
/// <param name="metadataProvider">The provider used for reading metadata for the model type.</param>
|
|
/// <param name="modelBinder">The <see cref="IModelBinder"/> used for binding.</param>
|
|
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
|
|
/// <param name="inputFormatters">
|
|
/// The set of <see cref="IInputFormatter"/> instances for deserializing the body.
|
|
/// </param>
|
|
/// <param name="objectModelValidator">The <see cref="IObjectModelValidator"/> used for validating the
|
|
/// bound values.</param>
|
|
/// <param name="validatorProvider">The <see cref="IModelValidatorProvider"/> used for executing validation
|
|
/// on the model
|
|
/// instance.</param>
|
|
/// <param name="includeExpressions">Expression(s) which represent top level properties
|
|
/// which need to be included for the current model.</param>
|
|
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful</returns>
|
|
public static Task<bool> TryUpdateModelAsync<TModel>(
|
|
[NotNull] TModel model,
|
|
[NotNull] string prefix,
|
|
[NotNull] HttpContext httpContext,
|
|
[NotNull] ModelStateDictionary modelState,
|
|
[NotNull] IModelMetadataProvider metadataProvider,
|
|
[NotNull] IModelBinder modelBinder,
|
|
[NotNull] IValueProvider valueProvider,
|
|
[NotNull] IList<IInputFormatter> inputFormatters,
|
|
[NotNull] IObjectModelValidator objectModelValidator,
|
|
[NotNull] IModelValidatorProvider validatorProvider,
|
|
[NotNull] params Expression<Func<TModel, object>>[] includeExpressions)
|
|
where TModel : class
|
|
{
|
|
var includeExpression = GetIncludePredicateExpression(prefix, includeExpressions);
|
|
var predicate = includeExpression.Compile();
|
|
|
|
return TryUpdateModelAsync(
|
|
model,
|
|
prefix,
|
|
httpContext,
|
|
modelState,
|
|
metadataProvider,
|
|
modelBinder,
|
|
valueProvider,
|
|
inputFormatters,
|
|
objectModelValidator,
|
|
validatorProvider,
|
|
predicate: predicate);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the specified <paramref name="model"/> instance using the specified <paramref name="modelBinder"/>
|
|
/// and the specified <paramref name="valueProvider"/> and executes validation using the specified
|
|
/// <paramref name="validatorProvider"/>.
|
|
/// </summary>
|
|
/// <typeparam name="TModel">The type of the model object.</typeparam>
|
|
/// <param name="model">The model instance to update and validate.</param>
|
|
/// <param name="prefix">The prefix to use when looking up values in the <paramref name="valueProvider"/>.
|
|
/// </param>
|
|
/// <param name="httpContext">The <see cref="HttpContext"/> for the current executing request.</param>
|
|
/// <param name="modelState">The <see cref="ModelStateDictionary"/> used for maintaining state and
|
|
/// results of model-binding validation.</param>
|
|
/// <param name="metadataProvider">The provider used for reading metadata for the model type.</param>
|
|
/// <param name="modelBinder">The <see cref="IModelBinder"/> used for binding.</param>
|
|
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
|
|
/// <param name="inputFormatters">
|
|
/// The set of <see cref="IInputFormatter"/> instances for deserializing the body.
|
|
/// </param>
|
|
/// <param name="objectModelValidator">The <see cref="IObjectModelValidator"/> used for validating the
|
|
/// bound values.</param>
|
|
/// <param name="validatorProvider">The <see cref="IModelValidatorProvider"/> used for executing validation
|
|
/// on the model instance.</param>
|
|
/// <param name="predicate">A predicate which can be used to
|
|
/// filter properties(for inclusion/exclusion) at runtime.</param>
|
|
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful</returns>
|
|
public static Task<bool> TryUpdateModelAsync<TModel>(
|
|
[NotNull] TModel model,
|
|
[NotNull] string prefix,
|
|
[NotNull] HttpContext httpContext,
|
|
[NotNull] ModelStateDictionary modelState,
|
|
[NotNull] IModelMetadataProvider metadataProvider,
|
|
[NotNull] IModelBinder modelBinder,
|
|
[NotNull] IValueProvider valueProvider,
|
|
[NotNull] IList<IInputFormatter> inputFormatters,
|
|
[NotNull] IObjectModelValidator objectModelValidator,
|
|
[NotNull] IModelValidatorProvider validatorProvider,
|
|
[NotNull] Func<ModelBindingContext, string, bool> predicate)
|
|
where TModel : class
|
|
{
|
|
return TryUpdateModelAsync(
|
|
model,
|
|
typeof(TModel),
|
|
prefix,
|
|
httpContext,
|
|
modelState,
|
|
metadataProvider,
|
|
modelBinder,
|
|
valueProvider,
|
|
inputFormatters,
|
|
objectModelValidator,
|
|
validatorProvider,
|
|
predicate: predicate);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the specified <paramref name="model"/> instance using the specified <paramref name="modelBinder"/>
|
|
/// and the specified <paramref name="valueProvider"/> and executes validation using the specified
|
|
/// <paramref name="validatorProvider"/>.
|
|
/// </summary>
|
|
/// <param name="model">The model instance to update and validate.</param>
|
|
/// <param name="modelType">The type of model instance to update and validate.</param>
|
|
/// <param name="prefix">The prefix to use when looking up values in the <paramref name="valueProvider"/>.
|
|
/// </param>
|
|
/// <param name="httpContext">The <see cref="HttpContext"/> for the current executing request.</param>
|
|
/// <param name="modelState">The <see cref="ModelStateDictionary"/> used for maintaining state and
|
|
/// results of model-binding validation.</param>
|
|
/// <param name="metadataProvider">The provider used for reading metadata for the model type.</param>
|
|
/// <param name="modelBinder">The <see cref="IModelBinder"/> used for binding.</param>
|
|
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
|
|
/// <param name="inputFormatters">
|
|
/// The set of <see cref="IInputFormatter"/> instances for deserializing the body.
|
|
/// </param>
|
|
/// <param name="objectModelValidator">The <see cref="IObjectModelValidator"/> used for validating the
|
|
/// bound values.</param>
|
|
/// <param name="validatorProvider">The <see cref="IModelValidatorProvider"/> used for executing validation
|
|
/// on the model instance.</param>
|
|
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful</returns>
|
|
public static Task<bool> TryUpdateModelAsync(
|
|
[NotNull] object model,
|
|
[NotNull] Type modelType,
|
|
[NotNull] string prefix,
|
|
[NotNull] HttpContext httpContext,
|
|
[NotNull] ModelStateDictionary modelState,
|
|
[NotNull] IModelMetadataProvider metadataProvider,
|
|
[NotNull] IModelBinder modelBinder,
|
|
[NotNull] IValueProvider valueProvider,
|
|
[NotNull] IList<IInputFormatter> inputFormatters,
|
|
[NotNull] IObjectModelValidator objectModelValidator,
|
|
[NotNull] IModelValidatorProvider validatorProvider)
|
|
{
|
|
// Includes everything by default.
|
|
return TryUpdateModelAsync(
|
|
model,
|
|
modelType,
|
|
prefix,
|
|
httpContext,
|
|
modelState,
|
|
metadataProvider,
|
|
modelBinder,
|
|
valueProvider,
|
|
inputFormatters,
|
|
objectModelValidator,
|
|
validatorProvider,
|
|
predicate: (context, propertyName) => true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the specified <paramref name="model"/> instance using the specified <paramref name="modelBinder"/>
|
|
/// and the specified <paramref name="valueProvider"/> and executes validation using the specified
|
|
/// <paramref name="validatorProvider"/>.
|
|
/// </summary>
|
|
/// <param name="model">The model instance to update and validate.</param>
|
|
/// <param name="modelType">The type of model instance to update and validate.</param>
|
|
/// <param name="prefix">The prefix to use when looking up values in the <paramref name="valueProvider"/>.
|
|
/// </param>
|
|
/// <param name="httpContext">The <see cref="HttpContext"/> for the current executing request.</param>
|
|
/// <param name="modelState">The <see cref="ModelStateDictionary"/> used for maintaining state and
|
|
/// results of model-binding validation.</param>
|
|
/// <param name="metadataProvider">The provider used for reading metadata for the model type.</param>
|
|
/// <param name="modelBinder">The <see cref="IModelBinder"/> used for binding.</param>
|
|
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
|
|
/// <param name="inputFormatters">
|
|
/// The set of <see cref="IInputFormatter"/> instances for deserializing the body.
|
|
/// </param>
|
|
/// <param name="objectModelValidator">The <see cref="IObjectModelValidator"/> used for validating the
|
|
/// bound values.</param>
|
|
/// <param name="validatorProvider">The <see cref="IModelValidatorProvider"/> used for executing validation
|
|
/// on the model instance.</param>
|
|
/// <param name="predicate">A predicate which can be used to
|
|
/// filter properties(for inclusion/exclusion) at runtime.</param>
|
|
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful</returns>
|
|
public static async Task<bool> TryUpdateModelAsync(
|
|
[NotNull] object model,
|
|
[NotNull] Type modelType,
|
|
[NotNull] string prefix,
|
|
[NotNull] HttpContext httpContext,
|
|
[NotNull] ModelStateDictionary modelState,
|
|
[NotNull] IModelMetadataProvider metadataProvider,
|
|
[NotNull] IModelBinder modelBinder,
|
|
[NotNull] IValueProvider valueProvider,
|
|
[NotNull] IList<IInputFormatter> inputFormatters,
|
|
[NotNull] IObjectModelValidator objectModelValidator,
|
|
[NotNull] IModelValidatorProvider validatorProvider,
|
|
[NotNull] Func<ModelBindingContext, string, bool> predicate)
|
|
{
|
|
if (!modelType.IsAssignableFrom(model.GetType()))
|
|
{
|
|
var message = Resources.FormatModelType_WrongType(
|
|
model.GetType().FullName,
|
|
modelType.FullName);
|
|
throw new ArgumentException(message, nameof(modelType));
|
|
}
|
|
|
|
var modelMetadata = metadataProvider.GetMetadataForType(modelType);
|
|
|
|
// Clear ModelStateDictionary entries for the model so that it will be re-validated.
|
|
ClearValidationStateForModel(modelType, modelState, metadataProvider, prefix);
|
|
|
|
var operationBindingContext = new OperationBindingContext
|
|
{
|
|
InputFormatters = inputFormatters,
|
|
ModelBinder = modelBinder,
|
|
ValidatorProvider = validatorProvider,
|
|
MetadataProvider = metadataProvider,
|
|
HttpContext = httpContext,
|
|
ValueProvider = valueProvider,
|
|
};
|
|
|
|
var modelBindingContext = ModelBindingContext.CreateBindingContext(
|
|
operationBindingContext,
|
|
modelState,
|
|
modelMetadata,
|
|
bindingInfo: null,
|
|
modelName: prefix ?? string.Empty);
|
|
modelBindingContext.Model = model;
|
|
modelBindingContext.PropertyFilter = predicate;
|
|
|
|
var modelBindingResult = await modelBinder.BindModelAsync(modelBindingContext);
|
|
if (modelBindingResult.IsModelSet)
|
|
{
|
|
objectModelValidator.Validate(
|
|
operationBindingContext.ValidatorProvider,
|
|
modelState,
|
|
modelBindingContext.ValidationState,
|
|
modelBindingResult.Key,
|
|
modelBindingResult.Model);
|
|
|
|
return modelState.IsValid;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Internal for tests
|
|
internal static string GetPropertyName(Expression expression)
|
|
{
|
|
if (expression.NodeType == ExpressionType.Convert ||
|
|
expression.NodeType == ExpressionType.ConvertChecked)
|
|
{
|
|
// For Boxed Value Types
|
|
expression = ((UnaryExpression)expression).Operand;
|
|
}
|
|
|
|
if (expression.NodeType != ExpressionType.MemberAccess)
|
|
{
|
|
throw new InvalidOperationException(Resources.FormatInvalid_IncludePropertyExpression(
|
|
expression.NodeType));
|
|
}
|
|
|
|
var memberExpression = (MemberExpression)expression;
|
|
var memberInfo = memberExpression.Member as PropertyInfo;
|
|
if (memberInfo != null)
|
|
{
|
|
if (memberExpression.Expression.NodeType != ExpressionType.Parameter)
|
|
{
|
|
// Chained expressions and non parameter based expressions are not supported.
|
|
throw new InvalidOperationException(
|
|
Resources.FormatInvalid_IncludePropertyExpression(expression.NodeType));
|
|
}
|
|
|
|
return memberInfo.Name;
|
|
}
|
|
else
|
|
{
|
|
// Fields are also not supported.
|
|
throw new InvalidOperationException(Resources.FormatInvalid_IncludePropertyExpression(
|
|
expression.NodeType));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an expression for a predicate to limit the set of properties used in model binding.
|
|
/// </summary>
|
|
/// <typeparam name="TModel">The model type.</typeparam>
|
|
/// <param name="prefix">The model prefix.</param>
|
|
/// <param name="expressions">Expressions identifying the properties to allow for binding.</param>
|
|
/// <returns>An expression which can be used with <see cref="IPropertyBindingPredicateProvider"/>.</returns>
|
|
public static Expression<Func<ModelBindingContext, string, bool>> GetIncludePredicateExpression<TModel>(
|
|
string prefix,
|
|
Expression<Func<TModel, object>>[] expressions)
|
|
{
|
|
if (expressions.Length == 0)
|
|
{
|
|
// If nothing is included explcitly, treat everything as included.
|
|
return (context, propertyName) => true;
|
|
}
|
|
|
|
var firstExpression = GetPredicateExpression(prefix, expressions[0]);
|
|
var orWrapperExpression = firstExpression.Body;
|
|
foreach (var expression in expressions.Skip(1))
|
|
{
|
|
var predicate = GetPredicateExpression(prefix, expression);
|
|
orWrapperExpression = Expression.OrElse(orWrapperExpression,
|
|
Expression.Invoke(predicate, firstExpression.Parameters));
|
|
}
|
|
|
|
return Expression.Lambda<Func<ModelBindingContext, string, bool>>(
|
|
orWrapperExpression, firstExpression.Parameters);
|
|
}
|
|
|
|
private static Expression<Func<ModelBindingContext, string, bool>> GetPredicateExpression<TModel>
|
|
(string prefix, Expression<Func<TModel, object>> expression)
|
|
{
|
|
var propertyName = GetPropertyName(expression.Body);
|
|
var property = ModelNames.CreatePropertyModelName(prefix, propertyName);
|
|
|
|
return
|
|
(context, modelPropertyName) =>
|
|
property.Equals(ModelNames.CreatePropertyModelName(context.ModelName, modelPropertyName),
|
|
StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears <see cref="ModelStateDictionary"/> entries for <see cref="ModelMetadata"/>.
|
|
/// </summary>
|
|
/// <param name="modelMetadata">The <see cref="ModelMetadata"/>.</param>
|
|
/// <param name="modelKey">The entry to clear. </param>
|
|
/// <param name="modelMetadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
|
|
public static void ClearValidationStateForModel(
|
|
[NotNull] Type modelType,
|
|
[NotNull] ModelStateDictionary modelstate,
|
|
[NotNull] IModelMetadataProvider metadataProvider,
|
|
string modelKey)
|
|
{
|
|
// If modelkey is empty, we need to iterate through properties (obtained from ModelMetadata) and
|
|
// clear validation state for all entries in ModelStateDictionary that start with each property name.
|
|
// If modelkey is non-empty, clear validation state for all entries in ModelStateDictionary
|
|
// that start with modelKey
|
|
if (string.IsNullOrEmpty(modelKey))
|
|
{
|
|
var modelMetadata = metadataProvider.GetMetadataForType(modelType);
|
|
var elementMetadata = modelMetadata.ElementMetadata;
|
|
if (elementMetadata != null)
|
|
{
|
|
modelMetadata = elementMetadata;
|
|
}
|
|
|
|
foreach (var property in modelMetadata.Properties)
|
|
{
|
|
var childKey = property.BinderModelName ?? property.PropertyName;
|
|
modelstate.ClearValidationState(childKey);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
modelstate.ClearValidationState(modelKey);
|
|
}
|
|
}
|
|
|
|
internal static void ValidateBindingContext([NotNull] ModelBindingContext bindingContext)
|
|
{
|
|
if (bindingContext.ModelMetadata == null)
|
|
{
|
|
throw new ArgumentException(Resources.ModelBinderUtil_ModelMetadataCannotBeNull, nameof(bindingContext));
|
|
}
|
|
}
|
|
|
|
internal static void ValidateBindingContext(
|
|
ModelBindingContext bindingContext,
|
|
Type requiredType,
|
|
bool allowNullModel)
|
|
{
|
|
ValidateBindingContext(bindingContext);
|
|
|
|
if (bindingContext.ModelType != requiredType)
|
|
{
|
|
var message = Resources.FormatModelBinderUtil_ModelTypeIsWrong(bindingContext.ModelType, requiredType);
|
|
throw new ArgumentException(message, nameof(bindingContext));
|
|
}
|
|
|
|
if (!allowNullModel && bindingContext.Model == null)
|
|
{
|
|
var message = Resources.FormatModelBinderUtil_ModelCannotBeNull(requiredType);
|
|
throw new ArgumentException(message, nameof(bindingContext));
|
|
}
|
|
|
|
if (bindingContext.Model != null &&
|
|
!bindingContext.ModelType.GetTypeInfo().IsAssignableFrom(requiredType.GetTypeInfo()))
|
|
{
|
|
var message = Resources.FormatModelBinderUtil_ModelInstanceIsWrong(
|
|
bindingContext.Model.GetType(),
|
|
requiredType);
|
|
throw new ArgumentException(message, nameof(bindingContext));
|
|
}
|
|
}
|
|
|
|
internal static TModel CastOrDefault<TModel>(object model)
|
|
{
|
|
return (model is TModel) ? (TModel)model : default(TModel);
|
|
}
|
|
|
|
public static object ConvertValuesToCollectionType<T>(Type modelType, IList<T> values)
|
|
{
|
|
// There's a limited set of collection types we can support here.
|
|
//
|
|
// For the simple cases - choose a T[] or List<T> if the destination type supports
|
|
// it.
|
|
//
|
|
// For more complex cases, if the destination type is a class and implements ICollection<T>
|
|
// then activate it and add the values.
|
|
//
|
|
// Otherwise just give up.
|
|
if (typeof(List<T>).IsAssignableFrom(modelType))
|
|
{
|
|
return new List<T>(values);
|
|
}
|
|
else if (typeof(T[]).IsAssignableFrom(modelType))
|
|
{
|
|
return values.ToArray();
|
|
}
|
|
else if (
|
|
modelType.GetTypeInfo().IsClass &&
|
|
!modelType.GetTypeInfo().IsAbstract &&
|
|
typeof(ICollection<T>).IsAssignableFrom(modelType))
|
|
{
|
|
var result = (ICollection<T>)Activator.CreateInstance(modelType);
|
|
foreach (var value in values)
|
|
{
|
|
result.Add(value);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
else if (typeof(IEnumerable<T>).IsAssignableFrom(modelType))
|
|
{
|
|
return values;
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts the provided <paramref name="value"/> to a value of <see cref="Type"/> <typeparamref name="T"/>
|
|
/// using the <see cref="CultureInfo.InvariantCulture"/>.
|
|
/// </summary>
|
|
/// <typeparam name="T">The <see cref="Type"/> for conversion.</typeparam>
|
|
/// <param name="value">The value to convert."/></param>
|
|
/// <returns>
|
|
/// The converted value or the default value of <typeparamref name="T"/> if the value could not be converted.
|
|
/// </returns>
|
|
public static T ConvertTo<T>(object value)
|
|
{
|
|
return ConvertTo<T>(value, culture: null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts the provided <paramref name="value"/> to a value of <see cref="Type"/> <typeparamref name="T"/>.
|
|
/// </summary>
|
|
/// <typeparam name="T">The <see cref="Type"/> for conversion.</typeparam>
|
|
/// <param name="value">The value to convert."/></param>
|
|
/// <param name="culture">The <see cref="CultureInfo"/> for conversion.</param>
|
|
/// <returns>
|
|
/// The converted value or the default value of <typeparamref name="T"/> if the value could not be converted.
|
|
/// </returns>
|
|
public static T ConvertTo<T>(object value, CultureInfo culture)
|
|
{
|
|
var converted = ConvertTo(value, typeof(T), culture);
|
|
return converted == null ? default(T) : (T)converted;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts the provided <paramref name="value"/> to a value of <see cref="Type"/> <param name="type"/>
|
|
/// using the <see cref="CultureInfo.InvariantCulture"/>.
|
|
/// </summary>
|
|
/// <param name="value">The value to convert."/></param>
|
|
/// <param name="type">The <see cref="Type"/> for conversion.</param>
|
|
/// <returns>
|
|
/// The converted value or <c>null</c> if the value could not be converted.
|
|
/// </returns>
|
|
public static object ConvertTo(object value, [NotNull] Type type)
|
|
{
|
|
return ConvertTo(value, type, culture: null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts the provided <paramref name="value"/> to a value of <see cref="Type"/> <paramref name="type"/>.
|
|
/// </summary>
|
|
/// <param name="value">The value to convert."/></param>
|
|
/// <param name="type">The <see cref="Type"/> for conversion.</param>
|
|
/// <param name="culture">The <see cref="CultureInfo"/> for conversion.</param>
|
|
/// <returns>
|
|
/// The converted value or <c>null</c> if the value could not be converted.
|
|
/// </returns>
|
|
public static object ConvertTo(object value, [NotNull] Type type, CultureInfo culture)
|
|
{
|
|
if (value == null)
|
|
{
|
|
// For value types, treat null values as though they were the default value for the type.
|
|
return type.GetTypeInfo().IsValueType ? Activator.CreateInstance(type) : null;
|
|
}
|
|
|
|
if (value.GetType().IsAssignableFrom(type))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
var cultureToUse = culture ?? CultureInfo.InvariantCulture;
|
|
return UnwrapPossibleArrayType(value, type, cultureToUse);
|
|
}
|
|
|
|
private static object UnwrapPossibleArrayType(object value, Type destinationType, CultureInfo culture)
|
|
{
|
|
// array conversion results in four cases, as below
|
|
var valueAsArray = value as Array;
|
|
if (destinationType.IsArray)
|
|
{
|
|
var destinationElementType = destinationType.GetElementType();
|
|
if (valueAsArray != null)
|
|
{
|
|
// case 1: both destination + source type are arrays, so convert each element
|
|
var converted = (IList)Array.CreateInstance(destinationElementType, valueAsArray.Length);
|
|
for (var i = 0; i < valueAsArray.Length; i++)
|
|
{
|
|
converted[i] = ConvertSimpleType(valueAsArray.GetValue(i), destinationElementType, culture);
|
|
}
|
|
return converted;
|
|
}
|
|
else
|
|
{
|
|
// case 2: destination type is array but source is single element, so wrap element in
|
|
// array + convert
|
|
var element = ConvertSimpleType(value, destinationElementType, culture);
|
|
var converted = (IList)Array.CreateInstance(destinationElementType, 1);
|
|
converted[0] = element;
|
|
return converted;
|
|
}
|
|
}
|
|
else if (valueAsArray != null)
|
|
{
|
|
// case 3: destination type is single element but source is array, so extract first element + convert
|
|
if (valueAsArray.Length > 0)
|
|
{
|
|
value = valueAsArray.GetValue(0);
|
|
return ConvertSimpleType(value, destinationType, culture);
|
|
}
|
|
else
|
|
{
|
|
// case 3(a): source is empty array, so can't perform conversion
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// case 4: both destination + source type are single elements, so convert
|
|
return ConvertSimpleType(value, destinationType, culture);
|
|
}
|
|
|
|
private static object ConvertSimpleType(object value, Type destinationType, CultureInfo culture)
|
|
{
|
|
if (value == null || value.GetType().IsAssignableFrom(destinationType))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
// In case of a Nullable object, we try again with its underlying type.
|
|
destinationType = UnwrapNullableType(destinationType);
|
|
|
|
// if this is a user-input value but the user didn't type anything, return no value
|
|
var valueAsString = value as string;
|
|
if (valueAsString != null && string.IsNullOrWhiteSpace(valueAsString))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var converter = TypeDescriptor.GetConverter(destinationType);
|
|
var canConvertFrom = converter.CanConvertFrom(value.GetType());
|
|
if (!canConvertFrom)
|
|
{
|
|
converter = TypeDescriptor.GetConverter(value.GetType());
|
|
}
|
|
if (!(canConvertFrom || converter.CanConvertTo(destinationType)))
|
|
{
|
|
// EnumConverter cannot convert integer, so we verify manually
|
|
if (destinationType.GetTypeInfo().IsEnum &&
|
|
(value is int ||
|
|
value is uint ||
|
|
value is long ||
|
|
value is ulong ||
|
|
value is short ||
|
|
value is ushort ||
|
|
value is byte ||
|
|
value is sbyte))
|
|
{
|
|
return Enum.ToObject(destinationType, value);
|
|
}
|
|
|
|
throw new InvalidOperationException(
|
|
Resources.FormatValueProviderResult_NoConverterExists(value.GetType(), destinationType));
|
|
}
|
|
|
|
try
|
|
{
|
|
return canConvertFrom
|
|
? converter.ConvertFrom(null, culture, value)
|
|
: converter.ConvertTo(null, culture, value, destinationType);
|
|
}
|
|
catch (FormatException)
|
|
{
|
|
throw;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (ex.InnerException == null)
|
|
{
|
|
throw;
|
|
}
|
|
else
|
|
{
|
|
// TypeConverter throws System.Exception wrapping the FormatException,
|
|
// so we throw the inner exception.
|
|
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
|
|
|
|
// This code is never reached because the previous line will always throw.
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static Type UnwrapNullableType(Type destinationType)
|
|
{
|
|
return Nullable.GetUnderlyingType(destinationType) ?? destinationType;
|
|
}
|
|
}
|
|
}
|