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)); } }
// 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); }
// 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 for testing. internal async Task <CollectionResult> BindSimpleCollection( ModelBindingContext bindingContext, ValueProviderResult values) { var boundCollection = new List <TElement>(); var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider; var elementMetadata = metadataProvider.GetMetadataForType(typeof(TElement)); var innerBindingContext = ModelBindingContext.CreateChildBindingContext( bindingContext, elementMetadata, fieldName: bindingContext.FieldName, modelName: bindingContext.ModelName, model: null); foreach (var value in values) { innerBindingContext.ValueProvider = new CompositeValueProvider { // our temporary provider goes at the front of the list new ElementalValueProvider(bindingContext.ModelName, value, values.Culture), bindingContext.ValueProvider }; object boundValue = null; var result = await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(innerBindingContext); if (result != null && result.IsModelSet) { boundValue = result.Model; boundCollection.Add(ModelBindingHelper.CastOrDefault <TElement>(boundValue)); } } return(new CollectionResult { Model = boundCollection }); }
private bool CanValueBindAnyModelProperties(MutableObjectBinderContext context) { // If there are no properties on the model, there is nothing to bind. We are here means this is not a top // level object. So we return false. if (context.PropertyMetadata == null || context.PropertyMetadata.Count == 0) { return(false); } // 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 fieldName = propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName; var modelName = ModelNames.CreatePropertyModelName( context.ModelBindingContext.ModelName, fieldName); var propertyModelBindingContext = ModelBindingContext.CreateChildBindingContext( context.ModelBindingContext, propertyMetadata, fieldName: fieldName, modelName: modelName, model: null); // If any property can return a true value. if (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); }
/// <inheritdoc /> public override async Task <ModelBindingResult> BindModelAsync([NotNull] ModelBindingContext bindingContext) { var result = await base.BindModelAsync(bindingContext); if (!result.IsModelSet) { // No match for the prefix at all. return(result); } Debug.Assert(result.Model != null); var model = (IDictionary <TKey, TValue>)result.Model; if (model.Count != 0) { // ICollection<KeyValuePair<TKey, TValue>> approach was successful. return(result); } var enumerableValueProvider = bindingContext.ValueProvider as IEnumerableValueProvider; if (enumerableValueProvider == null) { // No IEnumerableValueProvider available for the fallback approach. For example the user may have // replaced the ValueProvider with something other than a CompositeValueProvider. return(result); } // Attempt to bind dictionary from a set of prefix[key]=value entries. Get the short and long keys first. var keys = enumerableValueProvider.GetKeysFromPrefix(bindingContext.ModelName); if (!keys.Any()) { // No entries with the expected keys. return(result); } // Update the existing successful but empty ModelBindingResult. var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider; var valueMetadata = metadataProvider.GetMetadataForType(typeof(TValue)); var valueBindingContext = ModelBindingContext.CreateChildBindingContext( bindingContext, valueMetadata, fieldName: bindingContext.FieldName, modelName: bindingContext.ModelName, model: null); var modelBinder = bindingContext.OperationBindingContext.ModelBinder; var keyMappings = new Dictionary <string, TKey>(StringComparer.Ordinal); foreach (var kvp in keys) { // Use InvariantCulture to convert the key since ExpressionHelper.GetExpressionText() would use // that culture when rendering a form. var convertedKey = ModelBindingHelper.ConvertTo <TKey>(kvp.Key, culture: null); valueBindingContext.ModelName = kvp.Value; var valueResult = await modelBinder.BindModelAsync(valueBindingContext); // Always add an entry to the dictionary but validate only if binding was successful. model[convertedKey] = ModelBindingHelper.CastOrDefault <TValue>(valueResult.Model); keyMappings.Add(kvp.Key, convertedKey); } bindingContext.ValidationState.Add(model, new ValidationStateEntry() { Strategy = new ShortFormDictionaryValidationStrategy <TKey, TValue>(keyMappings, valueMetadata), }); return(result); }
// 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, }); }