internal async Task <ModelBindingResult> TryBindStrongModel <TModel>( ModelBindingContext parentBindingContext, string propertyName) { var propertyModelMetadata = parentBindingContext.OperationBindingContext.MetadataProvider.GetMetadataForType(typeof(TModel)); var propertyModelName = ModelNames.CreatePropertyModelName(parentBindingContext.ModelName, propertyName); var propertyBindingContext = ModelBindingContext.CreateChildBindingContext( parentBindingContext, propertyModelMetadata, propertyName, propertyModelName, model: null); var result = await propertyBindingContext.OperationBindingContext.ModelBinder.BindModelAsync( propertyBindingContext); if (result.IsModelSet) { return(result); } else { return(ModelBindingResult.Failed(propertyModelName)); } }
internal async Task <ModelBindingResult> TryBindStrongModel <TModel>( ModelBindingContext parentBindingContext, string propertyName, List <ModelValidationNode> childNodes) { var propertyModelMetadata = parentBindingContext.OperationBindingContext.MetadataProvider.GetMetadataForType(typeof(TModel)); var propertyModelName = ModelNames.CreatePropertyModelName(parentBindingContext.ModelName, propertyName); var propertyBindingContext = ModelBindingContext.GetChildModelBindingContext( parentBindingContext, propertyModelName, propertyModelMetadata); propertyBindingContext.BinderModelName = propertyModelMetadata.BinderModelName; var modelBindingResult = await propertyBindingContext.OperationBindingContext.ModelBinder.BindModelAsync( propertyBindingContext); if (modelBindingResult != null) { if (modelBindingResult.ValidationNode != null) { childNodes.Add(modelBindingResult.ValidationNode); } return(modelBindingResult); } // Always return a ModelBindingResult to avoid an NRE in BindModelAsync. return(new ModelBindingResult(model: default(TModel), key: propertyModelName, isModelSet: false)); }
// Used when the ValueProvider contains the collection to be bound as multiple elements, e.g. foo[0], foo[1]. private Task <CollectionResult> BindComplexCollection(ModelBindingContext bindingContext) { var indexPropertyName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, "index"); var valueProviderResultIndex = bindingContext.ValueProvider.GetValue(indexPropertyName); var indexNames = GetIndexNamesFromValueProviderResult(valueProviderResultIndex); return(BindComplexCollectionFromIndexes(bindingContext, indexNames)); }
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)); }
// Internal for testing. internal ModelValidationNode ProcessResults( ModelBindingContext bindingContext, IDictionary <ModelMetadata, ModelBindingResult> results, ModelValidationNode validationNode) { var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider; var modelExplorer = metadataProvider.GetModelExplorerForType(bindingContext.ModelType, bindingContext.Model); var validationInfo = GetPropertyValidationInfo(bindingContext); // Eliminate provided properties from RequiredProperties; leaving just *missing* required properties. var boundProperties = results.Where(p => p.Value.IsModelSet).Select(p => p.Key.PropertyName); validationInfo.RequiredProperties.ExceptWith(boundProperties); foreach (var missingRequiredProperty in validationInfo.RequiredProperties) { var propertyExplorer = modelExplorer.GetExplorerForProperty(missingRequiredProperty); var propertyName = propertyExplorer.Metadata.BinderModelName ?? missingRequiredProperty; var modelStateKey = ModelNames.CreatePropertyModelName(bindingContext.ModelName, propertyName); bindingContext.ModelState.TryAddModelError( modelStateKey, Resources.FormatModelBinding_MissingBindRequiredMember(propertyName)); } // For each property that BindPropertiesAsync() attempted to bind, call the setter, recording // exceptions as necessary. foreach (var entry in results) { var result = entry.Value; if (result != null) { var propertyMetadata = entry.Key; SetProperty(bindingContext, modelExplorer, propertyMetadata, result); var propertyValidationNode = result.ValidationNode; if (propertyValidationNode == null) { // Make sure that irrespective of whether the properties of the model were bound with a value, // create a validation node so that these get validated. propertyValidationNode = new ModelValidationNode(result.Key, entry.Key, result.Model); } validationNode.ChildNodes.Add(propertyValidationNode); } } return(validationNode); }
// Returned dictionary contains entries corresponding to properties against which binding was attempted. If // binding failed, the entry's value will have IsModelSet == false. Binding is attempted for all elements of // propertyMetadatas. private async Task <IDictionary <ModelMetadata, ModelBindingResult> > BindPropertiesAsync( ModelBindingContext bindingContext, IEnumerable <ModelMetadata> propertyMetadatas) { var results = new Dictionary <ModelMetadata, ModelBindingResult>(); foreach (var propertyMetadata in propertyMetadatas) { // ModelBindingContext.Model property values may be non-null when invoked via TryUpdateModel(). Pass // complex (including collection) values down so that binding system does not unnecessarily recreate // instances or overwrite inner properties that are not bound. No need for this with simple values // because they will be overwritten if binding succeeds. Arrays are never reused because they cannot // be resized. object model = null; if (propertyMetadata.PropertyGetter != null && propertyMetadata.IsComplexType && !propertyMetadata.ModelType.IsArray) { model = propertyMetadata.PropertyGetter(bindingContext.Model); } var fieldName = propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName; var modelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, fieldName); var propertyContext = ModelBindingContext.CreateChildBindingContext( bindingContext, propertyMetadata, fieldName: fieldName, modelName: modelName, model: model); var result = await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(propertyContext); if (result == ModelBindingResult.NoResult) { // Could not bind. Let ProcessResult() know explicitly. result = ModelBindingResult.Failed(propertyContext.ModelName); } results[propertyMetadata] = result; } return(results); }
// Returned dictionary contains entries corresponding to properties against which binding was attempted. If // binding failed, the entry's value will have IsModelSet == false. Binding is attempted for all elements of // propertyMetadatas. private async Task <IDictionary <ModelMetadata, ModelBindingResult> > BindPropertiesAsync( ModelBindingContext bindingContext, IEnumerable <ModelMetadata> propertyMetadatas) { var results = new Dictionary <ModelMetadata, ModelBindingResult>(); foreach (var propertyMetadata in propertyMetadatas) { var propertyModelName = ModelNames.CreatePropertyModelName( bindingContext.ModelName, propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName); var childContext = ModelBindingContext.GetChildModelBindingContext( bindingContext, propertyModelName, propertyMetadata); // ModelBindingContext.Model property values may be non-null when invoked via TryUpdateModel(). Pass // complex (including collection) values down so that binding system does not unnecessarily recreate // instances or overwrite inner properties that are not bound. No need for this with simple values // because they will be overwritten if binding succeeds. Arrays are never reused because they cannot // be resized. // // ModelMetadata.PropertyGetter is not null safe; use it only if Model is non-null. if (bindingContext.Model != null && propertyMetadata.PropertyGetter != null && propertyMetadata.IsComplexType && !propertyMetadata.ModelType.IsArray) { childContext.Model = propertyMetadata.PropertyGetter(bindingContext.Model); } var result = await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(childContext); if (result == null) { // Could not bind. Let ProcessResult() know explicitly. result = new ModelBindingResult(model: null, key: propertyModelName, isModelSet: false); } results[propertyMetadata] = result; } return(results); }
public async Task <ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext.ModelType != typeof(ComplexModelDto)) { return(null); } ModelBindingHelper.ValidateBindingContext(bindingContext, typeof(ComplexModelDto), allowNullModel: false); var dto = (ComplexModelDto)bindingContext.Model; foreach (var propertyMetadata in dto.PropertyMetadata) { var propertyModelName = ModelNames.CreatePropertyModelName( bindingContext.ModelName, propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName); var propertyBindingContext = ModelBindingContext.GetChildModelBindingContext( bindingContext, propertyModelName, propertyMetadata); var modelBindingResult = await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(propertyBindingContext); if (modelBindingResult == null) { // Could not bind. Add a result so MutableObjectModelBinder will check for [DefaultValue]. dto.Results[propertyMetadata] = new ModelBindingResult(model: null, key: propertyModelName, isModelSet: false); } else { dto.Results[propertyMetadata] = modelBindingResult; } } return(new ModelBindingResult(dto, bindingContext.ModelName, isModelSet: true)); }
// Internal for testing. internal void ProcessResults( ModelBindingContext bindingContext, IDictionary <ModelMetadata, ModelBindingResult> results) { var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider; var metadata = metadataProvider.GetMetadataForType(bindingContext.ModelType); var validationInfo = GetPropertyValidationInfo(bindingContext); // Eliminate provided properties from RequiredProperties; leaving just *missing* required properties. var boundProperties = results.Where(p => p.Value.IsModelSet).Select(p => p.Key.PropertyName); validationInfo.RequiredProperties.ExceptWith(boundProperties); foreach (var missingRequiredProperty in validationInfo.RequiredProperties) { var propertyMetadata = metadata.Properties[missingRequiredProperty]; var propertyName = propertyMetadata.BinderModelName ?? missingRequiredProperty; var modelStateKey = ModelNames.CreatePropertyModelName(bindingContext.ModelName, propertyName); bindingContext.ModelState.TryAddModelError( modelStateKey, bindingContext.ModelMetadata.ModelBindingMessageProvider.MissingBindRequiredValueAccessor( propertyName)); } // For each property that BindPropertiesAsync() attempted to bind, call the setter, recording // exceptions as necessary. foreach (var entry in results) { if (entry.Value != ModelBindingResult.NoResult) { var result = entry.Value; var propertyMetadata = entry.Key; SetProperty(bindingContext, metadata, propertyMetadata, result); } } }
// Internal for testing. internal ModelValidationNode ProcessDto( ModelBindingContext bindingContext, ComplexModelDto dto, ModelValidationNode validationNode) { var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider; var modelExplorer = metadataProvider.GetModelExplorerForType(bindingContext.ModelType, bindingContext.Model); var validationInfo = GetPropertyValidationInfo(bindingContext); // Eliminate provided properties from requiredProperties; leaving just *missing* required properties. var boundProperties = dto.Results.Where(p => p.Value.IsModelSet).Select(p => p.Key.PropertyName); validationInfo.RequiredProperties.ExceptWith(boundProperties); foreach (var missingRequiredProperty in validationInfo.RequiredProperties) { var addedError = false; // We want to provide the 'null' value, not the value of model.Property, // so avoiding modelExplorer.GetProperty here which would call the actual getter on the // model. This avoids issues with value types, or properties with pre-initialized values. var propertyExplorer = modelExplorer.GetExplorerForProperty(missingRequiredProperty, model: null); var propertyName = propertyExplorer.Metadata.BinderModelName ?? missingRequiredProperty; var modelStateKey = ModelNames.CreatePropertyModelName( bindingContext.ModelName, propertyName); // Get the first 'required' validator (if any) to get custom error message. var validatorProviderContext = new ModelValidatorProviderContext(propertyExplorer.Metadata); bindingContext.OperationBindingContext.ValidatorProvider.GetValidators(validatorProviderContext); var validator = validatorProviderContext.Validators.FirstOrDefault(v => v.IsRequired); if (validator != null) { addedError = RunValidator(validator, bindingContext, propertyExplorer, modelStateKey); } // Fall back to default message if BindingBehaviorAttribute required this property and we have no // actual validator for it. if (!addedError) { bindingContext.ModelState.TryAddModelError( modelStateKey, Resources.FormatModelBinding_MissingRequiredMember(propertyName)); } } // For each property that ComplexModelDtoModelBinder attempted to bind, call the setter, recording // exceptions as necessary. foreach (var entry in dto.Results) { var dtoResult = entry.Value; if (dtoResult != null) { var propertyMetadata = entry.Key; SetProperty(bindingContext, modelExplorer, propertyMetadata, dtoResult); var dtoValidationNode = dtoResult.ValidationNode; if (dtoValidationNode == null) { // Make sure that irrespective of if the properties of the model were bound with a value, // create a validation node so that these get validated. dtoValidationNode = new ModelValidationNode(dtoResult.Key, entry.Key, dtoResult.Model); } validationNode.ChildNodes.Add(dtoValidationNode); } } return(validationNode); }
private async Task <bool> CanValueBindAnyModelProperties(MutableObjectBinderContext context) { // We want to check to see if any of the properties of the model can be bound using the value providers, // because that's all that MutableObjectModelBinder can handle. // // However, because a property might specify a custom binding source ([FromForm]), it's not correct // for us to just try bindingContext.ValueProvider.ContainsPrefixAsync(bindingContext.ModelName), // because that may include ALL value providers - that would lead us to mistakenly create the model // when the data is coming from a source we should use (ex: value found in query string, but the // model has [FromForm]). // // To do this we need to enumerate the properties, and see which of them provide a binding source // through metadata, then we decide what to do. // // If a property has a binding source, and it's a greedy source, then it's not // allowed to come from a value provider, so we skip it. // // If a property has a binding source, and it's a non-greedy source, then we'll filter the // the value providers to just that source, and see if we can find a matching prefix // (see CanBindValue). // // If a property does not have a binding source, then it's fair game for any value provider. // // If any property meets the above conditions and has a value from valueproviders, then we'll // create the model and try to bind it. OR if ALL properties of the model have a greedy source, // then we go ahead and create it. // var isAnyPropertyEnabledForValueProviderBasedBinding = false; foreach (var propertyMetadata in context.PropertyMetadata) { // This check will skip properties which are marked explicitly using a non value binder. var bindingSource = propertyMetadata.BindingSource; if (bindingSource == null || !bindingSource.IsGreedy) { isAnyPropertyEnabledForValueProviderBasedBinding = true; var propertyModelName = ModelNames.CreatePropertyModelName( context.ModelBindingContext.ModelName, propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName); var propertyModelBindingContext = ModelBindingContext.GetChildModelBindingContext( context.ModelBindingContext, propertyModelName, propertyMetadata); // If any property can return a true value. if (await CanBindValue(propertyModelBindingContext)) { return(true); } } } if (!isAnyPropertyEnabledForValueProviderBasedBinding) { // Either there are no properties or all the properties are marked as // a non value provider based marker. // This would be the case when the model has all its properties annotated with // a IBinderMetadata. We want to be able to create such a model. return(true); } return(false); }
// Internal for testing. internal async Task <CollectionResult> BindComplexCollectionFromIndexes( ModelBindingContext bindingContext, IEnumerable <string> indexNames) { bool indexNamesIsFinite; if (indexNames != null) { indexNamesIsFinite = true; } else { indexNamesIsFinite = false; indexNames = Enumerable.Range(0, int.MaxValue) .Select(i => i.ToString(CultureInfo.InvariantCulture)); } var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider; var elementMetadata = metadataProvider.GetMetadataForType(typeof(TElement)); var boundCollection = new List <TElement>(); var validationNode = new ModelValidationNode( bindingContext.ModelName, bindingContext.ModelMetadata, boundCollection); foreach (var indexName in indexNames) { var fullChildName = ModelNames.CreateIndexModelName(bindingContext.ModelName, indexName); var childBindingContext = ModelBindingContext.GetChildModelBindingContext( bindingContext, fullChildName, elementMetadata); var didBind = false; object boundValue = null; var result = await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(childBindingContext); if (result != null && result.IsModelSet) { didBind = true; boundValue = result.Model; if (result.ValidationNode != null) { validationNode.ChildNodes.Add(result.ValidationNode); } } // infinite size collection stops on first bind failure if (!didBind && !indexNamesIsFinite) { break; } boundCollection.Add(ModelBindingHelper.CastOrDefault <TElement>(boundValue)); } return(new CollectionResult { ValidationNode = validationNode, Model = boundCollection }); }
// Internal for testing. internal async Task <CollectionResult> BindComplexCollectionFromIndexes( ModelBindingContext bindingContext, IEnumerable <string> indexNames) { bool indexNamesIsFinite; if (indexNames != null) { indexNamesIsFinite = true; } else { indexNamesIsFinite = false; indexNames = Enumerable.Range(0, int.MaxValue) .Select(i => i.ToString(CultureInfo.InvariantCulture)); } var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider; var elementMetadata = metadataProvider.GetMetadataForType(typeof(TElement)); var boundCollection = new List <TElement>(); foreach (var indexName in indexNames) { var fullChildName = ModelNames.CreateIndexModelName(bindingContext.ModelName, indexName); var childBindingContext = ModelBindingContext.CreateChildBindingContext( bindingContext, elementMetadata, fieldName: indexName, modelName: fullChildName, model: null); var didBind = false; object boundValue = null; var result = await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(childBindingContext); if (result != null && result.IsModelSet) { didBind = true; boundValue = result.Model; } // infinite size collection stops on first bind failure if (!didBind && !indexNamesIsFinite) { break; } boundCollection.Add(ModelBindingHelper.CastOrDefault <TElement>(boundValue)); } return(new CollectionResult { Model = boundCollection, // If we're working with a fixed set of indexes then this is the format like: // // ?parameter.index=zero,one,two¶meter[zero]=0&¶meter[one]=1¶meter[two]=2... // // We need to provide this data to the validation system so it can 'replay' the keys. // But we can't just set ValidationState here, because it needs the 'real' model. ValidationStrategy = indexNamesIsFinite ? new ExplicitIndexCollectionValidationStrategy(indexNames) : null, }); }