Imported Upstream version 3.6.0

Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
This commit is contained in:
Jo Shields
2014-08-13 10:39:27 +01:00
commit a575963da9
50588 changed files with 8155799 additions and 0 deletions

View File

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using System.Web.Http.Controllers;
namespace System.Web.Http.ModelBinding.Binders
{
public class ArrayModelBinder<TElement> : CollectionModelBinder<TElement>
{
protected override bool CreateOrReplaceCollection(HttpActionContext actionContext, ModelBindingContext bindingContext, IList<TElement> newCollection)
{
bindingContext.Model = newCollection.ToArray();
return true;
}
}
}

View File

@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Web.Http.Controllers;
namespace System.Web.Http.ModelBinding.Binders
{
public sealed class ArrayModelBinderProvider : ModelBinderProvider
{
public override IModelBinder GetBinder(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
ModelBindingHelper.ValidateBindingContext(bindingContext);
if (!bindingContext.ModelMetadata.IsReadOnly && bindingContext.ModelType.IsArray &&
bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName))
{
Type elementType = bindingContext.ModelType.GetElementType();
return (IModelBinder)Activator.CreateInstance(typeof(ArrayModelBinder<>).MakeGenericType(elementType));
}
return null;
}
}
}

View File

@ -0,0 +1,82 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Data.Linq;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Web.Http.Controllers;
using System.Web.Http.ValueProviders;
namespace System.Web.Http.ModelBinding.Binders
{
// This is a single provider that can work with both byte[] and Binary models.
public sealed class BinaryDataModelBinderProvider : ModelBinderProvider
{
private static readonly ModelBinderProvider[] _providers = new ModelBinderProvider[]
{
new SimpleModelBinderProvider(typeof(byte[]), new ByteArrayExtensibleModelBinder()),
new SimpleModelBinderProvider(typeof(Binary), new LinqBinaryExtensibleModelBinder())
};
public override IModelBinder GetBinder(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
return (from provider in _providers
let binder = provider.GetBinder(actionContext, bindingContext)
where binder != null
select binder).FirstOrDefault();
}
// This is essentially a clone of the ByteArrayModelBinder from core
private class ByteArrayExtensibleModelBinder : IModelBinder
{
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We want to ignore when the data is corrupted")]
[SuppressMessage("Microsoft.Globalization", "CA1304:SpecifyCultureInfo", MessageId = "System.Web.Http.ValueProviders.ValueProviderResult.ConvertTo(System.Type)", Justification = "The ValueProviderResult already has the necessary context to perform a culture-aware conversion.")]
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
ModelBindingHelper.ValidateBindingContext(bindingContext);
ValueProviderResult valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
// case 1: there was no <input ... /> element containing this data
if (valueProviderResult == null)
{
return false;
}
string base64String = (string)valueProviderResult.ConvertTo(typeof(string));
// case 2: there was an <input ... /> element but it was left blank
if (String.IsNullOrEmpty(base64String))
{
return false;
}
// Future proofing. If the byte array is actually an instance of System.Data.Linq.Binary
// then we need to remove these quotes put in place by the ToString() method.
string realValue = base64String.Replace("\"", String.Empty);
try
{
bindingContext.Model = ConvertByteArray(Convert.FromBase64String(realValue));
return true;
}
catch
{
// corrupt data - just ignore
return false;
}
}
protected virtual object ConvertByteArray(byte[] originalModel)
{
return originalModel;
}
}
// This is essentially a clone of the LinqBinaryModelBinder from core
private class LinqBinaryExtensibleModelBinder : ByteArrayExtensibleModelBinder
{
protected override object ConvertByteArray(byte[] originalModel)
{
return new Binary(originalModel);
}
}
}
}

View File

@ -0,0 +1,135 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Globalization;
using System.Web.Http.Controllers;
using System.Web.Http.Internal;
using System.Web.Http.ValueProviders;
using System.Web.Http.ValueProviders.Providers;
namespace System.Web.Http.ModelBinding.Binders
{
public class CollectionModelBinder<TElement> : IModelBinder
{
// Used when the ValueProvider contains the collection to be bound as multiple elements, e.g. foo[0], foo[1].
private static List<TElement> BindComplexCollection(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
string indexPropertyName = ModelBindingHelper.CreatePropertyModelName(bindingContext.ModelName, "index");
ValueProviderResult valueProviderResultIndex = bindingContext.ValueProvider.GetValue(indexPropertyName);
IEnumerable<string> indexNames = CollectionModelBinderUtil.GetIndexNamesFromValueProviderResult(valueProviderResultIndex);
return BindComplexCollectionFromIndexes(actionContext, bindingContext, indexNames);
}
internal static List<TElement> BindComplexCollectionFromIndexes(HttpActionContext actionContext, ModelBindingContext bindingContext, IEnumerable<string> indexNames)
{
bool indexNamesIsFinite;
if (indexNames != null)
{
indexNamesIsFinite = true;
}
else
{
indexNamesIsFinite = false;
indexNames = CollectionModelBinderUtil.GetZeroBasedIndexes();
}
List<TElement> boundCollection = new List<TElement>();
foreach (string indexName in indexNames)
{
string fullChildName = ModelBindingHelper.CreateIndexModelName(bindingContext.ModelName, indexName);
ModelBindingContext childBindingContext = new ModelBindingContext(bindingContext)
{
ModelMetadata = actionContext.GetMetadataProvider().GetMetadataForType(null, typeof(TElement)),
ModelName = fullChildName
};
bool didBind = false;
object boundValue = null;
IModelBinder childBinder;
if (actionContext.TryGetBinder(childBindingContext, out childBinder))
{
didBind = childBinder.BindModel(actionContext, childBindingContext);
if (didBind)
{
boundValue = childBindingContext.Model;
// merge validation up
bindingContext.ValidationNode.ChildNodes.Add(childBindingContext.ValidationNode);
}
}
// infinite size collection stops on first bind failure
if (!didBind && !indexNamesIsFinite)
{
break;
}
boundCollection.Add(ModelBindingHelper.CastOrDefault<TElement>(boundValue));
}
return boundCollection;
}
public virtual bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
ModelBindingHelper.ValidateBindingContext(bindingContext);
ValueProviderResult valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
List<TElement> boundCollection = (valueProviderResult != null)
? BindSimpleCollection(actionContext, bindingContext, valueProviderResult.RawValue, valueProviderResult.Culture)
: BindComplexCollection(actionContext, bindingContext);
bool retVal = CreateOrReplaceCollection(actionContext, bindingContext, boundCollection);
return retVal;
}
// Used when the ValueProvider contains the collection to be bound as a single element, e.g. the raw value
// is [ "1", "2" ] and needs to be converted to an int[].
internal static List<TElement> BindSimpleCollection(HttpActionContext actionContext, ModelBindingContext bindingContext, object rawValue, CultureInfo culture)
{
if (rawValue == null)
{
return null; // nothing to do
}
List<TElement> boundCollection = new List<TElement>();
object[] rawValueArray = ModelBindingHelper.RawValueToObjectArray(rawValue);
foreach (object rawValueElement in rawValueArray)
{
ModelBindingContext innerBindingContext = new ModelBindingContext(bindingContext)
{
ModelMetadata = actionContext.GetMetadataProvider().GetMetadataForType(null, typeof(TElement)),
ModelName = bindingContext.ModelName,
ValueProvider = new CompositeValueProvider
{
new ElementalValueProvider(bindingContext.ModelName, rawValueElement, culture), // our temporary provider goes at the front of the list
bindingContext.ValueProvider
}
};
object boundValue = null;
IModelBinder childBinder;
if (actionContext.TryGetBinder(innerBindingContext, out childBinder))
{
if (childBinder.BindModel(actionContext, innerBindingContext))
{
boundValue = innerBindingContext.Model;
bindingContext.ValidationNode.ChildNodes.Add(innerBindingContext.ValidationNode);
}
}
boundCollection.Add(ModelBindingHelper.CastOrDefault<TElement>(boundValue));
}
return boundCollection;
}
// Extensibility point that allows the bound collection to be manipulated or transformed before
// being returned from the binder.
protected virtual bool CreateOrReplaceCollection(HttpActionContext actionContext, ModelBindingContext bindingContext, IList<TElement> newCollection)
{
CollectionModelBinderUtil.CreateOrReplaceCollection(bindingContext, newCollection, () => new List<TElement>());
return true;
}
}
}

View File

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Web.Http.Controllers;
using System.Web.Http.Internal;
namespace System.Web.Http.ModelBinding.Binders
{
public sealed class CollectionModelBinderProvider : ModelBinderProvider
{
public override IModelBinder GetBinder(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
ModelBindingHelper.ValidateBindingContext(bindingContext);
if (bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName))
{
return CollectionModelBinderUtil.GetGenericBinder(typeof(ICollection<>), typeof(List<>), typeof(CollectionModelBinder<>), bindingContext.ModelMetadata);
}
else
{
return null;
}
}
}
}

View File

@ -0,0 +1,40 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Web.Http.Metadata;
namespace System.Web.Http.ModelBinding.Binders
{
// Describes a complex model, but uses a collection rather than individual properties as the data store.
public class ComplexModelDto
{
public ComplexModelDto(ModelMetadata modelMetadata, IEnumerable<ModelMetadata> propertyMetadata)
{
if (modelMetadata == null)
{
throw Error.ArgumentNull("modelMetadata");
}
if (propertyMetadata == null)
{
throw Error.ArgumentNull("propertyMetadata");
}
ModelMetadata = modelMetadata;
PropertyMetadata = new Collection<ModelMetadata>(propertyMetadata.ToList());
Results = new Dictionary<ModelMetadata, ComplexModelDtoResult>();
}
public ModelMetadata ModelMetadata { get; private set; }
public Collection<ModelMetadata> PropertyMetadata { get; private set; }
// Contains entries corresponding to each property against which binding was
// attempted. If binding failed, the entry's value will be null. If binding
// was never attempted, this dictionary will not contain a corresponding
// entry.
public IDictionary<ModelMetadata, ComplexModelDtoResult> Results { get; private set; }
}
}

View File

@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Web.Http.Controllers;
using System.Web.Http.Metadata;
namespace System.Web.Http.ModelBinding.Binders
{
public sealed class ComplexModelDtoModelBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
ModelBindingHelper.ValidateBindingContext(bindingContext, typeof(ComplexModelDto), false /* allowNullModel */);
ComplexModelDto dto = (ComplexModelDto)bindingContext.Model;
foreach (ModelMetadata propertyMetadata in dto.PropertyMetadata)
{
ModelBindingContext propertyBindingContext = new ModelBindingContext(bindingContext)
{
ModelMetadata = propertyMetadata,
ModelName = ModelBindingHelper.CreatePropertyModelName(bindingContext.ModelName, propertyMetadata.PropertyName)
};
// bind and propagate the values
IModelBinder propertyBinder;
if (actionContext.TryGetBinder(propertyBindingContext, out propertyBinder))
{
if (propertyBinder.BindModel(actionContext, propertyBindingContext))
{
dto.Results[propertyMetadata] = new ComplexModelDtoResult(propertyBindingContext.Model, propertyBindingContext.ValidationNode);
}
else
{
dto.Results[propertyMetadata] = null;
}
}
}
return true;
}
}
}

View File

@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Web.Http.Controllers;
namespace System.Web.Http.ModelBinding.Binders
{
// Returns a binder that can bind ComplexModelDto objects.
public sealed class ComplexModelDtoModelBinderProvider : ModelBinderProvider
{
// This is really just a simple binder.
private static readonly SimpleModelBinderProvider _underlyingProvider = GetUnderlyingProvider();
public override IModelBinder GetBinder(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
return _underlyingProvider.GetBinder(actionContext, bindingContext);
}
private static SimpleModelBinderProvider GetUnderlyingProvider()
{
return new SimpleModelBinderProvider(typeof(ComplexModelDto), new ComplexModelDtoModelBinder())
{
SuppressPrefixCheck = true
};
}
}
}

View File

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Web.Http.Validation;
namespace System.Web.Http.ModelBinding.Binders
{
public sealed class ComplexModelDtoResult
{
public ComplexModelDtoResult(object model, ModelValidationNode validationNode)
{
if (validationNode == null)
{
throw Error.ArgumentNull("validationNode");
}
Model = model;
ValidationNode = validationNode;
}
public object Model { get; private set; }
public ModelValidationNode ValidationNode { get; private set; }
}
}

View File

@ -0,0 +1,148 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Web.Http.Controllers;
using System.Web.Http.Internal;
using System.Web.Http.Properties;
namespace System.Web.Http.ModelBinding.Binders
{
/// <summary>
/// This class is an <see cref="IModelBinder"/> that delegates to one of a collection of
/// <see cref="ModelBinderProvider"/> instances.
/// </summary>
/// <remarks>
/// If no binder is available and the <see cref="ModelBindingContext"/> allows it,
/// this class tries to find a binder using an empty prefix.
/// </remarks>
public class CompositeModelBinder : IModelBinder
{
public CompositeModelBinder(IEnumerable<ModelBinderProvider> modelBinderProviders)
: this(modelBinderProviders.ToArray())
{
}
public CompositeModelBinder(ModelBinderProvider[] modelBinderProviders)
{
Providers = modelBinderProviders;
}
private ModelBinderProvider[] Providers { get; set; }
public virtual bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
//// REVIEW: from MVC Futures
////CheckPropertyFilter(bindingContext);
ModelBindingContext newBindingContext = CreateNewBindingContext(bindingContext, bindingContext.ModelName);
IModelBinder binder = GetBinder(actionContext, newBindingContext);
if (binder == null && !String.IsNullOrEmpty(bindingContext.ModelName)
&& bindingContext.FallbackToEmptyPrefix)
{
// fallback to empty prefix?
newBindingContext = CreateNewBindingContext(bindingContext, String.Empty /* modelName */);
binder = GetBinder(actionContext, newBindingContext);
}
if (binder != null)
{
bool boundSuccessfully = binder.BindModel(actionContext, newBindingContext);
if (boundSuccessfully)
{
// run validation and return the model
// If we fell back to an empty prefix above and are dealing with simple types,
// propagate the non-blank model name through for user clarity in validation errors.
// Complex types will reveal their individual properties as model names and do not require this.
if (!newBindingContext.ModelMetadata.IsComplexType && String.IsNullOrEmpty(newBindingContext.ModelName))
{
newBindingContext.ValidationNode = new Validation.ModelValidationNode(newBindingContext.ModelMetadata, bindingContext.ModelName);
}
newBindingContext.ValidationNode.Validate(actionContext, null /* parentNode */);
bindingContext.Model = newBindingContext.Model;
return true;
}
}
return false; // something went wrong
}
//// REVIEW: from MVC Futures -- activate when Filters are in
////private static void CheckPropertyFilter(ModelBindingContext bindingContext)
////{
//// if (bindingContext.ModelType.GetProperties().Select(p => p.Name).Any(name => !bindingContext.PropertyFilter(name)))
//// {
//// throw Error.InvalidOperation(SRResources.ExtensibleModelBinderAdapter_PropertyFilterMustNotBeSet);
//// }
////}
private IModelBinder GetBinder(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
Contract.Assert(actionContext != null);
Contract.Assert(bindingContext != null);
//// REVIEW: this was in MVC Futures
////EnsureNoBindAttribute(bindingContext.ModelType);
ModelBinderProvider providerFromAttr;
if (TryGetProviderFromAttributes(bindingContext.ModelType, out providerFromAttr))
{
return providerFromAttr.GetBinder(actionContext, bindingContext);
}
return (from provider in Providers
let binder = provider.GetBinder(actionContext, bindingContext)
where binder != null
select binder).FirstOrDefault();
}
private static ModelBindingContext CreateNewBindingContext(ModelBindingContext oldBindingContext, string modelName)
{
ModelBindingContext newBindingContext = new ModelBindingContext
{
ModelMetadata = oldBindingContext.ModelMetadata,
ModelName = modelName,
ModelState = oldBindingContext.ModelState,
ValueProvider = oldBindingContext.ValueProvider
};
// validation is expensive to create, so copy it over if we can
if (object.ReferenceEquals(modelName, oldBindingContext.ModelName))
{
newBindingContext.ValidationNode = oldBindingContext.ValidationNode;
}
return newBindingContext;
}
private static bool TryGetProviderFromAttributes(Type modelType, out ModelBinderProvider provider)
{
ModelBinderAttribute attr = TypeDescriptorHelper.Get(modelType).GetAttributes().OfType<ModelBinderAttribute>().FirstOrDefault();
if (attr == null)
{
provider = null;
return false;
}
// TODO, 386718, remove the following if statement when the bug is resolved
if (attr.BinderType == null)
{
provider = null;
return false;
}
if (typeof(ModelBinderProvider).IsAssignableFrom(attr.BinderType))
{
provider = (ModelBinderProvider)Activator.CreateInstance(attr.BinderType);
}
else
{
throw Error.InvalidOperation(SRResources.ModelBinderProviderCollection_InvalidBinderType, attr.BinderType.Name, typeof(ModelBinderProvider).Name, typeof(IModelBinder).Name);
}
return true;
}
}
}

View File

@ -0,0 +1,47 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using System.Web.Http.Controllers;
namespace System.Web.Http.ModelBinding.Binders
{
public sealed class CompositeModelBinderProvider : ModelBinderProvider
{
private ModelBinderProvider[] _providers;
public CompositeModelBinderProvider()
{
}
public CompositeModelBinderProvider(IEnumerable<ModelBinderProvider> providers)
{
if (providers == null)
{
throw Error.ArgumentNull("providers");
}
_providers = providers.ToArray();
}
public IEnumerable<ModelBinderProvider> Providers
{
get { return _providers; }
}
public override IModelBinder GetBinder(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
// Fast-path the case where we already have the providers.
if (_providers != null)
{
return new CompositeModelBinder(_providers);
}
// Extract all providers from the resolver except the the type of the executing one (else would cause recursion),
// or use the set of providers we were given.
IEnumerable<ModelBinderProvider> providers = actionContext.ControllerContext.Configuration.Services.GetModelBinderProviders().Where(p => !(p is CompositeModelBinderProvider));
return new CompositeModelBinder(providers);
}
}
}

View File

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Web.Http.Controllers;
using System.Web.Http.Internal;
namespace System.Web.Http.ModelBinding.Binders
{
public class DictionaryModelBinder<TKey, TValue> : CollectionModelBinder<KeyValuePair<TKey, TValue>>
{
protected override bool CreateOrReplaceCollection(HttpActionContext actionContext, ModelBindingContext bindingContext, IList<KeyValuePair<TKey, TValue>> newCollection)
{
CollectionModelBinderUtil.CreateOrReplaceDictionary(bindingContext, newCollection, () => new Dictionary<TKey, TValue>());
return true;
}
}
}

View File

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Web.Http.Controllers;
using System.Web.Http.Internal;
namespace System.Web.Http.ModelBinding.Binders
{
public sealed class DictionaryModelBinderProvider : ModelBinderProvider
{
public override IModelBinder GetBinder(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
ModelBindingHelper.ValidateBindingContext(bindingContext);
if (bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName))
{
return CollectionModelBinderUtil.GetGenericBinder(typeof(IDictionary<,>), typeof(Dictionary<,>), typeof(DictionaryModelBinder<,>), bindingContext.ModelMetadata);
}
else
{
return null;
}
}
}
}

View File

@ -0,0 +1,138 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Web.Http.Controllers;
using System.Web.Http.Internal;
using System.Web.Http.Properties;
namespace System.Web.Http.ModelBinding.Binders
{
// Returns a user-specified binder for a given open generic type.
public sealed class GenericModelBinderProvider : ModelBinderProvider
{
private readonly Func<Type[], IModelBinder> _modelBinderFactory;
private readonly Type _modelType;
public GenericModelBinderProvider(Type modelType, IModelBinder modelBinder)
{
if (modelType == null)
{
throw Error.ArgumentNull("modelType");
}
if (modelBinder == null)
{
throw Error.ArgumentNull("modelBinder");
}
ValidateParameters(modelType, null /* modelBinderType */);
_modelType = modelType;
_modelBinderFactory = _ => modelBinder;
}
public GenericModelBinderProvider(Type modelType, Type modelBinderType)
{
// The binder can be a closed type, in which case it will be instantiated directly. If the binder
// is an open type, the type arguments will be determined at runtime and the corresponding closed
// type instantiated.
if (modelType == null)
{
throw Error.ArgumentNull("modelType");
}
if (modelBinderType == null)
{
throw Error.ArgumentNull("modelBinderType");
}
ValidateParameters(modelType, modelBinderType);
bool modelBinderTypeIsOpenGeneric = modelBinderType.IsGenericTypeDefinition;
_modelType = modelType;
_modelBinderFactory = typeArguments =>
{
Type closedModelBinderType = modelBinderTypeIsOpenGeneric ? modelBinderType.MakeGenericType(typeArguments) : modelBinderType;
return (IModelBinder)Activator.CreateInstance(closedModelBinderType);
};
}
public GenericModelBinderProvider(Type modelType, Func<Type[], IModelBinder> modelBinderFactory)
{
if (modelType == null)
{
throw Error.ArgumentNull("modelType");
}
if (modelBinderFactory == null)
{
throw Error.ArgumentNull("modelBinderFactory");
}
ValidateParameters(modelType, null /* modelBinderType */);
_modelType = modelType;
_modelBinderFactory = modelBinderFactory;
}
public Type ModelType
{
get { return _modelType; }
}
public bool SuppressPrefixCheck { get; set; }
public override IModelBinder GetBinder(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
ModelBindingHelper.ValidateBindingContext(bindingContext);
Type[] typeArguments = null;
if (ModelType.IsInterface)
{
Type matchingClosedInterface = TypeHelper.ExtractGenericInterface(bindingContext.ModelType, ModelType);
if (matchingClosedInterface != null)
{
typeArguments = matchingClosedInterface.GetGenericArguments();
}
}
else
{
typeArguments = TypeHelper.GetTypeArgumentsIfMatch(bindingContext.ModelType, ModelType);
}
if (typeArguments != null)
{
if (SuppressPrefixCheck || bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName))
{
return _modelBinderFactory(typeArguments);
}
}
return null;
}
private static void ValidateParameters(Type modelType, Type modelBinderType)
{
if (!modelType.IsGenericTypeDefinition)
{
throw Error.Argument("modelType", SRResources.GenericModelBinderProvider_ParameterMustSpecifyOpenGenericType, modelType);
}
if (modelBinderType != null)
{
if (!typeof(IModelBinder).IsAssignableFrom(modelBinderType))
{
throw Error.Argument("modelBinderType", SRResources.Common_TypeMustImplementInterface, modelBinderType, typeof(IModelBinder));
}
if (modelBinderType.IsGenericTypeDefinition)
{
if (modelType.GetGenericArguments().Length != modelBinderType.GetGenericArguments().Length)
{
throw Error.Argument("modelBinderType",
SRResources.GenericModelBinderProvider_TypeArgumentCountMismatch,
modelType,
modelType.GetGenericArguments().Length,
modelBinderType,
modelBinderType.GetGenericArguments().Length);
}
}
}
}
}
}

View File

@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Web.Http.Controllers;
using System.Web.Http.Internal;
using System.Web.Http.Metadata;
namespace System.Web.Http.ModelBinding.Binders
{
public sealed class KeyValuePairModelBinder<TKey, TValue> : IModelBinder
{
internal ModelMetadataProvider MetadataProvider { private get; set; }
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
ModelMetadataProvider metadataProvider = MetadataProvider ?? actionContext.GetMetadataProvider();
ModelBindingHelper.ValidateBindingContext(bindingContext, typeof(KeyValuePair<TKey, TValue>), true /* allowNullModel */);
TKey key;
bool keyBindingSucceeded = actionContext.TryBindStrongModel(bindingContext, "key", metadataProvider, out key);
TValue value;
bool valueBindingSucceeded = actionContext.TryBindStrongModel(bindingContext, "value", metadataProvider, out value);
if (keyBindingSucceeded && valueBindingSucceeded)
{
bindingContext.Model = new KeyValuePair<TKey, TValue>(key, value);
}
return keyBindingSucceeded || valueBindingSucceeded;
}
}
}

View File

@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Web.Http.Controllers;
namespace System.Web.Http.ModelBinding.Binders
{
public sealed class KeyValuePairModelBinderProvider : ModelBinderProvider
{
public override IModelBinder GetBinder(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
ModelBindingHelper.ValidateBindingContext(bindingContext);
string keyFieldName = ModelBindingHelper.CreatePropertyModelName(bindingContext.ModelName, "key");
string valueFieldName = ModelBindingHelper.CreatePropertyModelName(bindingContext.ModelName, "value");
if (bindingContext.ValueProvider.ContainsPrefix(keyFieldName) && bindingContext.ValueProvider.ContainsPrefix(valueFieldName))
{
return ModelBindingHelper.GetPossibleBinderInstance(bindingContext.ModelType, typeof(KeyValuePair<,>) /* supported model type */, typeof(KeyValuePairModelBinder<,>) /* binder type */);
}
else
{
// 'key' or 'value' missing
return null;
}
}
}
}

View File

@ -0,0 +1,267 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Web.Http.Controllers;
using System.Web.Http.Internal;
using System.Web.Http.Metadata;
using System.Web.Http.Properties;
using System.Web.Http.Validation;
namespace System.Web.Http.ModelBinding.Binders
{
public class MutableObjectModelBinder : IModelBinder
{
internal ModelMetadataProvider MetadataProvider { private get; set; }
public virtual bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
ModelBindingHelper.ValidateBindingContext(bindingContext);
EnsureModel(actionContext, bindingContext);
IEnumerable<ModelMetadata> propertyMetadatas = GetMetadataForProperties(actionContext, bindingContext);
ComplexModelDto dto = CreateAndPopulateDto(actionContext, bindingContext, propertyMetadatas);
// post-processing, e.g. property setters and hooking up validation
ProcessDto(actionContext, bindingContext, dto);
bindingContext.ValidationNode.ValidateAllProperties = true; // complex models require full validation
return true;
}
protected virtual bool CanUpdateProperty(ModelMetadata propertyMetadata)
{
return CanUpdatePropertyInternal(propertyMetadata);
}
internal static bool CanUpdatePropertyInternal(ModelMetadata propertyMetadata)
{
return !propertyMetadata.IsReadOnly || CanUpdateReadOnlyProperty(propertyMetadata.ModelType);
}
private static bool CanUpdateReadOnlyProperty(Type propertyType)
{
// Value types have copy-by-value semantics, which prevents us from updating
// properties that are marked readonly.
if (propertyType.IsValueType)
{
return false;
}
// Arrays are strange beasts since their contents are mutable but their sizes aren't.
// Therefore we shouldn't even try to update these. Further reading:
// http://blogs.msdn.com/ericlippert/archive/2008/09/22/arrays-considered-somewhat-harmful.aspx
if (propertyType.IsArray)
{
return false;
}
// Special-case known immutable reference types
if (propertyType == typeof(string))
{
return false;
}
return true;
}
private ComplexModelDto CreateAndPopulateDto(HttpActionContext actionContext, ModelBindingContext bindingContext, IEnumerable<ModelMetadata> propertyMetadatas)
{
ModelMetadataProvider metadataProvider = MetadataProvider ?? actionContext.GetMetadataProvider();
// create a DTO and call into the DTO binder
ComplexModelDto originalDto = new ComplexModelDto(bindingContext.ModelMetadata, propertyMetadatas);
ModelBindingContext dtoBindingContext = new ModelBindingContext(bindingContext)
{
ModelMetadata = metadataProvider.GetMetadataForType(() => originalDto, typeof(ComplexModelDto)),
ModelName = bindingContext.ModelName
};
IModelBinder dtoBinder = actionContext.GetBinder(dtoBindingContext);
dtoBinder.BindModel(actionContext, dtoBindingContext);
return (ComplexModelDto)dtoBindingContext.Model;
}
protected virtual object CreateModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
// If the Activator throws an exception, we want to propagate it back up the call stack, since the application
// developer should know that this was an invalid type to try to bind to.
return Activator.CreateInstance(bindingContext.ModelType);
}
// Called when the property setter null check failed, allows us to add our own error message to ModelState.
internal static EventHandler<ModelValidatedEventArgs> CreateNullCheckFailedHandler(ModelMetadata modelMetadata, object incomingValue)
{
return (sender, e) =>
{
ModelValidationNode validationNode = (ModelValidationNode)sender;
ModelStateDictionary modelState = e.ActionContext.ModelState;
if (modelState.IsValidField(validationNode.ModelStateKey))
{
string errorMessage = ModelBinderConfig.ValueRequiredErrorMessageProvider(e.ActionContext, modelMetadata, incomingValue);
if (errorMessage != null)
{
modelState.AddModelError(validationNode.ModelStateKey, errorMessage);
}
}
};
}
protected virtual void EnsureModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
if (bindingContext.Model == null)
{
bindingContext.ModelMetadata.Model = CreateModel(actionContext, bindingContext);
}
}
protected virtual IEnumerable<ModelMetadata> GetMetadataForProperties(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
// keep a set of the required properties so that we can cross-reference bound properties later
HashSet<string> requiredProperties;
Dictionary<string, ModelValidator> requiredValidators;
HashSet<string> skipProperties;
GetRequiredPropertiesCollection(actionContext, bindingContext, out requiredProperties, out requiredValidators, out skipProperties);
return from propertyMetadata in bindingContext.ModelMetadata.Properties
let propertyName = propertyMetadata.PropertyName
let shouldUpdateProperty = requiredProperties.Contains(propertyName) || !skipProperties.Contains(propertyName)
where shouldUpdateProperty && CanUpdateProperty(propertyMetadata)
select propertyMetadata;
}
private static object GetPropertyDefaultValue(PropertyDescriptor propertyDescriptor)
{
DefaultValueAttribute attr = propertyDescriptor.Attributes.OfType<DefaultValueAttribute>().FirstOrDefault();
return (attr != null) ? attr.Value : null;
}
internal static void GetRequiredPropertiesCollection(HttpActionContext actionContext, ModelBindingContext bindingContext, out HashSet<string> requiredProperties, out Dictionary<string, ModelValidator> requiredValidators, out HashSet<string> skipProperties)
{
requiredProperties = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
requiredValidators = new Dictionary<string, ModelValidator>(StringComparer.OrdinalIgnoreCase);
skipProperties = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
// Use attributes on the property before attributes on the type.
ICustomTypeDescriptor modelDescriptor = TypeDescriptorHelper.Get(bindingContext.ModelType);
PropertyDescriptorCollection propertyDescriptors = modelDescriptor.GetProperties();
HttpBindingBehaviorAttribute typeAttr = modelDescriptor.GetAttributes().OfType<HttpBindingBehaviorAttribute>().SingleOrDefault();
foreach (PropertyDescriptor propertyDescriptor in propertyDescriptors)
{
string propertyName = propertyDescriptor.Name;
ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[propertyName];
ModelValidator requiredValidator = actionContext.GetValidators(propertyMetadata).Where(v => v.IsRequired).FirstOrDefault();
requiredValidators[propertyName] = requiredValidator;
HttpBindingBehaviorAttribute propAttr = propertyDescriptor.Attributes.OfType<HttpBindingBehaviorAttribute>().SingleOrDefault();
HttpBindingBehaviorAttribute workingAttr = propAttr ?? typeAttr;
if (workingAttr != null)
{
switch (workingAttr.Behavior)
{
case HttpBindingBehavior.Required:
requiredProperties.Add(propertyName);
break;
case HttpBindingBehavior.Never:
skipProperties.Add(propertyName);
break;
}
}
else if (requiredValidator != null)
{
requiredProperties.Add(propertyName);
}
}
}
internal void ProcessDto(HttpActionContext actionContext, ModelBindingContext bindingContext, ComplexModelDto dto)
{
HashSet<string> requiredProperties;
Dictionary<string, ModelValidator> requiredValidators;
HashSet<string> skipProperties;
GetRequiredPropertiesCollection(actionContext, bindingContext, out requiredProperties, out requiredValidators, out skipProperties);
// Are all of the required fields accounted for?
HashSet<string> missingRequiredProperties = new HashSet<string>(requiredProperties.Except(dto.Results.Select(r => r.Key.PropertyName)));
foreach (string missingRequiredProperty in missingRequiredProperties)
{
string key = ModelBindingHelper.CreatePropertyModelName(bindingContext.ValidationNode.ModelStateKey, missingRequiredProperty);
bindingContext.ModelState.AddModelError(key, Error.Format(SRResources.MissingRequiredMember, missingRequiredProperty));
}
// for each property that was bound, call the setter, recording exceptions as necessary
foreach (var entry in dto.Results)
{
ModelMetadata propertyMetadata = entry.Key;
ComplexModelDtoResult dtoResult = entry.Value;
if (dtoResult != null)
{
SetProperty(actionContext, bindingContext, propertyMetadata, dtoResult, requiredValidators[propertyMetadata.PropertyName]);
bindingContext.ValidationNode.ChildNodes.Add(dtoResult.ValidationNode);
}
}
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We're recording this exception so that we can act on it later.")]
protected virtual void SetProperty(HttpActionContext actionContext, ModelBindingContext bindingContext, ModelMetadata propertyMetadata, ComplexModelDtoResult dtoResult, ModelValidator requiredValidator)
{
PropertyDescriptor propertyDescriptor = TypeDescriptorHelper.Get(bindingContext.ModelType).GetProperties().Find(propertyMetadata.PropertyName, true /* ignoreCase */);
if (propertyDescriptor == null || propertyDescriptor.IsReadOnly)
{
return; // nothing to do
}
object value = dtoResult.Model ?? GetPropertyDefaultValue(propertyDescriptor);
propertyMetadata.Model = value;
// 'Required' validators need to run first so that we can provide useful error messages if
// the property setters throw, e.g. if we're setting entity keys to null. See comments in
// DefaultModelBinder.SetProperty() for more information.
if (value == null)
{
string modelStateKey = dtoResult.ValidationNode.ModelStateKey;
if (bindingContext.ModelState.IsValidField(modelStateKey))
{
if (requiredValidator != null)
{
foreach (ModelValidationResult validationResult in requiredValidator.Validate(propertyMetadata, bindingContext.Model))
{
bindingContext.ModelState.AddModelError(modelStateKey, validationResult.Message);
}
}
}
}
if (value != null || TypeHelper.TypeAllowsNullValue(propertyDescriptor.PropertyType))
{
try
{
propertyDescriptor.SetValue(bindingContext.Model, value);
}
catch (Exception ex)
{
// don't display a duplicate error message if a binding error has already occurred for this field
string modelStateKey = dtoResult.ValidationNode.ModelStateKey;
if (bindingContext.ModelState.IsValidField(modelStateKey))
{
bindingContext.ModelState.AddModelError(modelStateKey, ex);
}
}
}
else
{
// trying to set a non-nullable value type to null, need to make sure there's a message
string modelStateKey = dtoResult.ValidationNode.ModelStateKey;
if (bindingContext.ModelState.IsValidField(modelStateKey))
{
dtoResult.ValidationNode.Validated += CreateNullCheckFailedHandler(propertyMetadata, value);
}
}
}
}
}

View File

@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Web.Http.Controllers;
namespace System.Web.Http.ModelBinding.Binders
{
public sealed class MutableObjectModelBinderProvider : ModelBinderProvider
{
public override IModelBinder GetBinder(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
ModelBindingHelper.ValidateBindingContext(bindingContext);
if (!bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName))
{
// no values to bind
return null;
}
// Simple types cannot use this binder
if (!bindingContext.ModelMetadata.IsComplexType)
{
return null;
}
if (bindingContext.ModelType == typeof(ComplexModelDto))
{
// forbidden type - will cause a stack overflow if we try binding this type
return null;
}
return new MutableObjectModelBinder();
}
}
}

View File

@ -0,0 +1,65 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Web.Http.Controllers;
namespace System.Web.Http.ModelBinding.Binders
{
// Returns a user-specified binder for a given type.
public sealed class SimpleModelBinderProvider : ModelBinderProvider
{
private readonly Func<IModelBinder> _modelBinderFactory;
private readonly Type _modelType;
public SimpleModelBinderProvider(Type modelType, IModelBinder modelBinder)
{
if (modelType == null)
{
throw Error.ArgumentNull("modelType");
}
if (modelBinder == null)
{
throw Error.ArgumentNull("modelBinder");
}
_modelType = modelType;
_modelBinderFactory = () => modelBinder;
}
public SimpleModelBinderProvider(Type modelType, Func<IModelBinder> modelBinderFactory)
{
if (modelType == null)
{
throw Error.ArgumentNull("modelType");
}
if (modelBinderFactory == null)
{
throw Error.ArgumentNull("modelBinderFactory");
}
_modelType = modelType;
_modelBinderFactory = modelBinderFactory;
}
public Type ModelType
{
get { return _modelType; }
}
public bool SuppressPrefixCheck { get; set; }
public override IModelBinder GetBinder(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
ModelBindingHelper.ValidateBindingContext(bindingContext);
if (bindingContext.ModelType == ModelType)
{
if (SuppressPrefixCheck || bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName))
{
return _modelBinderFactory();
}
}
return null;
}
}
}

View File

@ -0,0 +1,64 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Diagnostics.CodeAnalysis;
using System.Web.Http.Controllers;
using System.Web.Http.ValueProviders;
namespace System.Web.Http.ModelBinding.Binders
{
public sealed class TypeConverterModelBinder : IModelBinder
{
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The exception is recorded to be acted upon later.")]
[SuppressMessage("Microsoft.Globalization", "CA1304:SpecifyCultureInfo", MessageId = "System.Web.Http.ValueProviders.ValueProviderResult.ConvertTo(System.Type)", Justification = "The ValueProviderResult already has the necessary context to perform a culture-aware conversion.")]
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
ModelBindingHelper.ValidateBindingContext(bindingContext);
ValueProviderResult valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult == null)
{
return false; // no entry
}
object newModel;
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
try
{
newModel = valueProviderResult.ConvertTo(bindingContext.ModelType);
}
catch (Exception ex)
{
if (IsFormatException(ex))
{
// there was a type conversion failure
string errorString = ModelBinderConfig.TypeConversionErrorMessageProvider(actionContext, bindingContext.ModelMetadata, valueProviderResult.AttemptedValue);
if (errorString != null)
{
bindingContext.ModelState.AddModelError(bindingContext.ModelName, errorString);
}
}
else
{
bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex);
}
return false;
}
ModelBindingHelper.ReplaceEmptyStringWithNull(bindingContext.ModelMetadata, ref newModel);
bindingContext.Model = newModel;
return true;
}
private static bool IsFormatException(Exception ex)
{
for (; ex != null; ex = ex.InnerException)
{
if (ex is FormatException)
{
return true;
}
}
return false;
}
}
}

Some files were not shown because too many files have changed in this diff Show More