You've already forked linux-packaging-mono
Imported Upstream version 4.0.0~alpha1
Former-commit-id: 806294f5ded97629b74c85c09952f2a74fe182d9
This commit is contained in:
229
external/referencesource/System.Web/ModelBinding/MutableObjectModelBinder.cs
vendored
Normal file
229
external/referencesource/System.Web/ModelBinding/MutableObjectModelBinder.cs
vendored
Normal file
@ -0,0 +1,229 @@
|
||||
namespace System.Web.ModelBinding {
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
public class MutableObjectModelBinder : IModelBinder {
|
||||
|
||||
private ModelMetadataProvider _metadataProvider;
|
||||
|
||||
internal ModelMetadataProvider MetadataProvider {
|
||||
get {
|
||||
if (_metadataProvider == null) {
|
||||
_metadataProvider = ModelMetadataProviders.Current;
|
||||
}
|
||||
return _metadataProvider;
|
||||
}
|
||||
set {
|
||||
_metadataProvider = value;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool BindModel(ModelBindingExecutionContext modelBindingExecutionContext, ModelBindingContext bindingContext) {
|
||||
// Recursive method - prevent stack overflow.
|
||||
RuntimeHelpers.EnsureSufficientExecutionStack();
|
||||
|
||||
ModelBinderUtil.ValidateBindingContext(bindingContext);
|
||||
|
||||
EnsureModel(modelBindingExecutionContext, bindingContext);
|
||||
IEnumerable<ModelMetadata> propertyMetadatas = GetMetadataForProperties(modelBindingExecutionContext, bindingContext);
|
||||
ComplexModel complexModel = CreateAndPopulateComplexModel(modelBindingExecutionContext, bindingContext, propertyMetadatas);
|
||||
|
||||
// post-processing, e.g. property setters and hooking up validation
|
||||
ProcessComplexModel(modelBindingExecutionContext, bindingContext, complexModel);
|
||||
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 ComplexModel CreateAndPopulateComplexModel(ModelBindingExecutionContext modelBindingExecutionContext, ModelBindingContext bindingContext, IEnumerable<ModelMetadata> propertyMetadatas) {
|
||||
// create a Complex Model and call into the Complex Model binder
|
||||
ComplexModel originalComplexModel = new ComplexModel(bindingContext.ModelMetadata, propertyMetadatas);
|
||||
ModelBindingContext complexModelBindingContext = new ModelBindingContext(bindingContext) {
|
||||
ModelMetadata = MetadataProvider.GetMetadataForType(() => originalComplexModel, typeof(ComplexModel)),
|
||||
ModelName = bindingContext.ModelName
|
||||
};
|
||||
|
||||
IModelBinder complexModelBinder = bindingContext.ModelBinderProviders.GetRequiredBinder(modelBindingExecutionContext, complexModelBindingContext);
|
||||
complexModelBinder.BindModel(modelBindingExecutionContext, complexModelBindingContext);
|
||||
return (ComplexModel)complexModelBindingContext.Model;
|
||||
}
|
||||
|
||||
protected virtual object CreateModel(ModelBindingExecutionContext modelBindingExecutionContext, 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 SecurityUtils.SecureCreateInstance(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(ModelBindingExecutionContext modelBindingExecutionContext, ModelMetadata modelMetadata, object incomingValue) {
|
||||
return (sender, e) =>
|
||||
{
|
||||
ModelValidationNode validationNode = (ModelValidationNode)sender;
|
||||
ModelStateDictionary modelState = e.ModelBindingExecutionContext.ModelState;
|
||||
|
||||
if (modelState.IsValidField(validationNode.ModelStateKey)) {
|
||||
string errorMessage = ModelBinderErrorMessageProviders.ValueRequiredErrorMessageProvider(modelBindingExecutionContext, modelMetadata, incomingValue);
|
||||
if (errorMessage != null) {
|
||||
modelState.AddModelError(validationNode.ModelStateKey, errorMessage);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected virtual void EnsureModel(ModelBindingExecutionContext modelBindingExecutionContext, ModelBindingContext bindingContext) {
|
||||
if (bindingContext.Model == null) {
|
||||
bindingContext.ModelMetadata.Model = CreateModel(modelBindingExecutionContext, bindingContext);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual IEnumerable<ModelMetadata> GetMetadataForProperties(ModelBindingExecutionContext modelBindingExecutionContext, ModelBindingContext bindingContext) {
|
||||
// keep a set of the required properties so that we can cross-reference bound properties later
|
||||
HashSet<string> requiredProperties;
|
||||
HashSet<string> skipProperties;
|
||||
GetRequiredPropertiesCollection(bindingContext.ModelType, out requiredProperties, 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(Type modelType, out HashSet<string> requiredProperties, out HashSet<string> skipProperties) {
|
||||
requiredProperties = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
skipProperties = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// Use attributes on the property before attributes on the type.
|
||||
ICustomTypeDescriptor modelDescriptor = TypeDescriptorHelper.Get(modelType);
|
||||
PropertyDescriptorCollection propertyDescriptors = modelDescriptor.GetProperties();
|
||||
BindingBehaviorAttribute typeAttr = modelDescriptor.GetAttributes().OfType<BindingBehaviorAttribute>().SingleOrDefault();
|
||||
|
||||
foreach (PropertyDescriptor propertyDescriptor in propertyDescriptors) {
|
||||
BindingBehaviorAttribute propAttr = propertyDescriptor.Attributes.OfType<BindingBehaviorAttribute>().SingleOrDefault();
|
||||
BindingBehaviorAttribute workingAttr = propAttr ?? typeAttr;
|
||||
if (workingAttr != null) {
|
||||
switch (workingAttr.Behavior) {
|
||||
case BindingBehavior.Required:
|
||||
requiredProperties.Add(propertyDescriptor.Name);
|
||||
break;
|
||||
|
||||
case BindingBehavior.Never:
|
||||
skipProperties.Add(propertyDescriptor.Name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void ProcessComplexModel(ModelBindingExecutionContext modelBindingExecutionContext, ModelBindingContext bindingContext, ComplexModel complexModel) {
|
||||
HashSet<string> requiredProperties;
|
||||
HashSet<string> skipProperties;
|
||||
GetRequiredPropertiesCollection(bindingContext.ModelType, out requiredProperties, out skipProperties);
|
||||
|
||||
// Are all of the required fields accounted for?
|
||||
HashSet<string> missingRequiredProperties = new HashSet<string>(requiredProperties);
|
||||
missingRequiredProperties.ExceptWith(complexModel.Results.Select(r => r.Key.PropertyName));
|
||||
string missingPropertyName = missingRequiredProperties.FirstOrDefault();
|
||||
if (missingPropertyName != null) {
|
||||
string fullPropertyKey = ModelBinderUtil.CreatePropertyModelName(bindingContext.ModelName, missingPropertyName);
|
||||
throw Error.BindingBehavior_ValueNotFound(fullPropertyKey);
|
||||
}
|
||||
|
||||
// for each property that was bound, call the setter, recording exceptions as necessary
|
||||
foreach (var entry in complexModel.Results) {
|
||||
ModelMetadata propertyMetadata = entry.Key;
|
||||
|
||||
ComplexModelResult complexModelResult = entry.Value;
|
||||
if (complexModelResult != null) {
|
||||
SetProperty(modelBindingExecutionContext, bindingContext, propertyMetadata, complexModelResult);
|
||||
bindingContext.ValidationNode.ChildNodes.Add(complexModelResult.ValidationNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We're recording this exception so that we can act on it later.")]
|
||||
protected virtual void SetProperty(ModelBindingExecutionContext modelBindingExecutionContext, ModelBindingContext bindingContext, ModelMetadata propertyMetadata, ComplexModelResult complexModelResult) {
|
||||
PropertyDescriptor propertyDescriptor = TypeDescriptorHelper.Get(bindingContext.ModelType).GetProperties().Find(propertyMetadata.PropertyName, true /* ignoreCase */);
|
||||
if (propertyDescriptor == null || propertyDescriptor.IsReadOnly) {
|
||||
return; // nothing to do
|
||||
}
|
||||
|
||||
object value = complexModelResult.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 = complexModelResult.ValidationNode.ModelStateKey;
|
||||
if (bindingContext.ModelState.IsValidField(modelStateKey)) {
|
||||
ModelValidator requiredValidator = ModelValidatorProviders.Providers.GetValidators(propertyMetadata, modelBindingExecutionContext).Where(v => v.IsRequired).FirstOrDefault();
|
||||
if (requiredValidator != null) {
|
||||
foreach (ModelValidationResult validationResult in requiredValidator.Validate(bindingContext.Model)) {
|
||||
bindingContext.ModelState.AddModelError(modelStateKey, validationResult.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (value != null || TypeHelpers.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 = complexModelResult.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 = complexModelResult.ValidationNode.ModelStateKey;
|
||||
if (bindingContext.ModelState.IsValidField(modelStateKey)) {
|
||||
complexModelResult.ValidationNode.Validated += CreateNullCheckFailedHandler(modelBindingExecutionContext, propertyMetadata, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user