internal async Task <ModelBindingResult> TryBindStrongModel <TModel>(ModelBindingContext parentBindingContext, string propertyName) { var propertyModelMetadata = parentBindingContext.OperationBindingContext.MetadataProvider.GetMetadataForType(typeof(TModel)); var propertyModelName = ModelBindingHelper.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) { 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 a single element, e.g. the raw value // is [ "1", "2" ] and needs to be converted to an int[]. internal async Task <CollectionResult> BindSimpleCollection( ModelBindingContext bindingContext, object rawValue, CultureInfo culture) { if (rawValue == null) { return(null); // nothing to do } var boundCollection = new List <TElement>(); var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider; var elementMetadata = metadataProvider.GetMetadataForType(typeof(TElement)); var validationNode = new ModelValidationNode( bindingContext.ModelName, bindingContext.ModelMetadata, boundCollection); var rawValueArray = RawValueToObjectArray(rawValue); foreach (var rawValueElement in rawValueArray) { var innerBindingContext = ModelBindingContext.GetChildModelBindingContext( bindingContext, bindingContext.ModelName, elementMetadata); innerBindingContext.ValueProvider = new CompositeValueProvider { // our temporary provider goes at the front of the list new ElementalValueProvider(bindingContext.ModelName, rawValueElement, culture), bindingContext.ValueProvider }; object boundValue = null; var result = await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(innerBindingContext); if (result != null && result.IsModelSet) { boundValue = result.Model; if (result.ValidationNode != null) { validationNode.ChildNodes.Add(result.ValidationNode); } } boundCollection.Add(ModelBindingHelper.CastOrDefault <TElement>(boundValue)); } return(new CollectionResult { ValidationNode = validationNode, Model = boundCollection }); }
internal async Task <IEnumerable <TElement> > BindComplexCollectionFromIndexes(ModelBindingContext bindingContext, IEnumerable <string> indexNames) { bool indexNamesIsFinite; if (indexNames != null) { indexNamesIsFinite = true; } else { indexNamesIsFinite = false; indexNames = Enumerable.Range(0, Int32.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 = ModelBindingHelper.CreateIndexModelName(bindingContext.ModelName, indexName); var childBindingContext = ModelBindingContext.GetChildModelBindingContext( bindingContext, fullChildName, elementMetadata); var didBind = false; object boundValue = null; var modelType = bindingContext.ModelType; var result = await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(childBindingContext); if (result != null) { didBind = true; boundValue = result.Model; } // infinite size collection stops on first bind failure if (!didBind && !indexNamesIsFinite) { break; } boundCollection.Add(ModelBindingHelper.CastOrDefault <TElement>(boundValue)); } return(boundCollection); }
// 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); }
private async Task <ModelBindingResult> CreateAndPopulateDto( ModelBindingContext bindingContext, IEnumerable <ModelMetadata> propertyMetadatas) { // create a DTO and call into the DTO binder var dto = new ComplexModelDto(bindingContext.ModelMetadata, propertyMetadatas); var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider; var dtoMetadata = metadataProvider.GetMetadataForType(typeof(ComplexModelDto)); var childContext = ModelBindingContext.GetChildModelBindingContext( bindingContext, bindingContext.ModelName, dtoMetadata); childContext.Model = dto; return(await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(childContext)); }
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)); }
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); }
/// <inheritdoc /> public override async Task <ModelBindingResult> BindModelAsync([NotNull] ModelBindingContext bindingContext) { var result = await base.BindModelAsync(bindingContext); if (result == null || !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 = await enumerableValueProvider.GetKeysFromPrefixAsync(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.GetChildModelBindingContext( bindingContext, bindingContext.ModelName, valueMetadata); var modelBinder = bindingContext.OperationBindingContext.ModelBinder; var validationNode = result.ValidationNode; foreach (var key in keys) { var dictionaryKey = ConvertFromString(key.Key); valueBindingContext.ModelName = key.Value; var valueResult = await modelBinder.BindModelAsync(valueBindingContext); // Always add an entry to the dictionary but validate only if binding was successful. model[dictionaryKey] = ModelBindingHelper.CastOrDefault <TValue>(valueResult?.Model); if (valueResult != null && valueResult.IsModelSet) { validationNode.ChildNodes.Add(valueResult.ValidationNode); } } return(result); }