/// <summary> /// Binds the model. /// </summary> public async Task BindModelAsync(ModelBindingContext bindingContext) { var typeFieldNamePrefix = !string.IsNullOrEmpty(bindingContext.ModelName) ? $"{bindingContext.ModelName}." : ""; var typeName = (string)bindingContext.ValueProvider .GetValue($"{typeFieldNamePrefix}{_baseType.Name}Type") .ConvertTo(typeof(string)); if (typeName == null) return; var derivedModelBinderKvp = _derivedModelBinders.First(kvp => kvp.Key.Name == typeName); var derivedModelType = derivedModelBinderKvp.Key; var derivedModelBinder = derivedModelBinderKvp.Value; ModelBindingResult result; using (bindingContext.EnterNestedScope( _modelMetadataProvider.GetMetadataForType(derivedModelType), bindingContext.FieldName, bindingContext.ModelName, model: null )) { await derivedModelBinder.BindModelAsync(bindingContext); result = bindingContext.Result; } bindingContext.Result = result; }
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)); } } }
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); }
// 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)); foreach (var value in values) { bindingContext.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; using (bindingContext.EnterNestedScope( elementMetadata, fieldName: bindingContext.FieldName, modelName: bindingContext.ModelName, model: null)) { await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(bindingContext); if (bindingContext.Result != null && bindingContext.Result.Value.IsModelSet) { boundValue = bindingContext.Result.Value.Model; boundCollection.Add(ModelBindingHelper.CastOrDefault <TElement>(boundValue)); } } } return(new CollectionResult { Model = boundCollection }); }
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); }
/// <inheritdoc /> public override async Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); } await base.BindModelAsync(bindingContext); if (bindingContext.Result == null || !bindingContext.Result.Value.IsModelSet) { // No match for the prefix at all. return; } var result = bindingContext.Result.Value; Debug.Assert(result.Model != null); var model = (IDictionary <TKey, TValue>)result.Model; if (model.Count != 0) { // ICollection<KeyValuePair<TKey, TValue>> approach was successful. return; } 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; } // 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; } // Update the existing successful but empty ModelBindingResult. var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider; var valueMetadata = metadataProvider.GetMetadataForType(typeof(TValue)); 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); using (bindingContext.EnterNestedScope( modelMetadata: valueMetadata, fieldName: bindingContext.FieldName, modelName: kvp.Value, model: null)) { await modelBinder.BindModelAsync(bindingContext); var valueResult = bindingContext.Result; // Always add an entry to the dictionary but validate only if binding was successful. model[convertedKey] = ModelBindingHelper.CastOrDefault <TValue>(valueResult.Value.Model); keyMappings.Add(kvp.Key, convertedKey); } } bindingContext.ValidationState.Add(model, new ValidationStateEntry() { Strategy = new ShortFormDictionaryValidationStrategy <TKey, TValue>(keyMappings, valueMetadata), }); }
private async Task RunModelBinders(ModelBindingContext bindingContext) { RuntimeHelpers.EnsureSufficientExecutionStack(); ModelBindingResult?overallResult = null; try { using (bindingContext.EnterNestedScope()) { if (PrepareBindingContext(bindingContext)) { // Perf: Avoid allocations for (var i = 0; i < ModelBinders.Count; i++) { var binder = ModelBinders[i]; await binder.BindModelAsync(bindingContext); if (bindingContext.Result != null) { var result = bindingContext.Result.Value; // This condition is necessary because the ModelState entry would never be validated if // caller fell back to the empty prefix, leading to an possibly-incorrect !IsValid. In most // (hopefully all) cases, the ModelState entry exists because some binders add errors before // returning a result with !IsModelSet. Those binders often cannot run twice anyhow. if (result.IsModelSet || bindingContext.ModelState.ContainsKey(bindingContext.ModelName)) { if (bindingContext.IsTopLevelObject && result.Model != null) { ValidationStateEntry entry; if (!bindingContext.ValidationState.TryGetValue(result.Model, out entry)) { entry = new ValidationStateEntry() { Key = result.Key, Metadata = bindingContext.ModelMetadata, }; bindingContext.ValidationState.Add(result.Model, entry); } } overallResult = bindingContext.Result; return; } // Current binder should have been able to bind value but found nothing. Exit loop in a way that // tells caller to fall back to the empty prefix, if appropriate. Do not return result because it // means only "other binders are not applicable". // overallResult MUST still be null at this return statement. return; } } } } } finally { bindingContext.Result = overallResult; } }
// 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, }); }