internal async Task <ModelBindingResult> TryBindStrongModel <TModel>( ModelBindingContext bindingContext, string propertyName) { var propertyModelMetadata = bindingContext.OperationBindingContext.MetadataProvider.GetMetadataForType(typeof(TModel)); var propertyModelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, propertyName); using (bindingContext.EnterNestedScope( modelMetadata: propertyModelMetadata, fieldName: propertyName, modelName: propertyModelName, model: null)) { await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync( bindingContext); var result = bindingContext.Result; if (result != null && result.Value.IsModelSet) { return(result.Value); } else { return(ModelBindingResult.Failed(propertyModelName)); } } }
// 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 async Task BindModelCoreAsync(ModelBindingContext bindingContext) { // Create model first (if necessary) to avoid reporting errors about properties when activation fails. if (bindingContext.Model == null) { bindingContext.Model = CreateModel(bindingContext); } foreach (var property in bindingContext.ModelMetadata.Properties) { if (!CanBindProperty(bindingContext, property)) { continue; } // 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 propertyModel = null; if (property.PropertyGetter != null && property.IsComplexType && !property.ModelType.IsArray) { propertyModel = property.PropertyGetter(bindingContext.Model); } var fieldName = property.BinderModelName ?? property.PropertyName; var modelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, fieldName); ModelBindingResult result; using (bindingContext.EnterNestedScope( modelMetadata: property, fieldName: fieldName, modelName: modelName, model: propertyModel)) { await BindProperty(bindingContext); result = bindingContext.Result ?? ModelBindingResult.Failed(modelName); } if (result.IsModelSet) { SetProperty(bindingContext, property, result); } else if (property.IsBindingRequired) { var message = property.ModelBindingMessageProvider.MissingBindRequiredValueAccessor(fieldName); bindingContext.ModelState.TryAddModelError(modelName, message); } } bindingContext.Result = ModelBindingResult.Success(bindingContext.ModelName, bindingContext.Model); }
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)); }
public async Task CollectionModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(string prefix) { // Arrange var binder = new CollectionModelBinder <string>(); var context = CreateContext(); context.ModelName = ModelNames.CreatePropertyModelName(prefix, "ListProperty"); var metadataProvider = context.OperationBindingContext.MetadataProvider; context.ModelMetadata = metadataProvider.GetMetadataForProperty( typeof(ModelWithListProperty), nameof(ModelWithListProperty.ListProperty)); context.ValueProvider = new TestValueProvider(new Dictionary <string, object>()); // Act var result = await binder.BindModelResultAsync(context); // Assert Assert.Equal(default(ModelBindingResult), result); }
private bool CanValueBindAnyModelProperties(ModelBindingContext bindingContext) { // 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 (bindingContext.ModelMetadata.Properties.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 other 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 hasBindableProperty = false; var isAnyPropertyEnabledForValueProviderBasedBinding = false; foreach (var propertyMetadata in bindingContext.ModelMetadata.Properties) { if (!CanBindProperty(bindingContext, propertyMetadata)) { continue; } hasBindableProperty = true; // 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( bindingContext.ModelName, fieldName); using (bindingContext.EnterNestedScope( modelMetadata: propertyMetadata, fieldName: fieldName, modelName: modelName, model: null)) { // If any property can return a true value. if (CanBindValue(bindingContext)) { return(true); } } } } if (hasBindableProperty && !isAnyPropertyEnabledForValueProviderBasedBinding) { // All the properties are marked with a non value provider based marker like [FromHeader] or // [FromBody]. 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>(); foreach (var indexName in indexNames) { var fullChildName = ModelNames.CreateIndexModelName(bindingContext.ModelName, indexName); var didBind = false; object boundValue = null; ModelBindingResult?result; using (bindingContext.EnterNestedScope( elementMetadata, fieldName: indexName, modelName: fullChildName, model: null)) { await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(bindingContext); result = bindingContext.Result; } if (result != null && result.Value.IsModelSet) { didBind = true; boundValue = result.Value.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, }); }