// 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 elementMetadata = bindingContext.ModelMetadata.ElementMetadata; 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 }; // Enter new scope to change ModelMetadata and isolate element binding operations. using (bindingContext.EnterNestedScope( elementMetadata, fieldName: bindingContext.FieldName, modelName: bindingContext.ModelName, model: null)) { await ElementBinder.BindModelAsync(bindingContext); if (bindingContext.Result.IsModelSet) { var boundValue = bindingContext.Result.Model; boundCollection.Add(CastOrDefault <TElement>(boundValue)); } } } return(new CollectionResult { 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 elementMetadata = bindingContext.ModelMetadata.ElementMetadata; var boundCollection = new List <TElement>(); foreach (var indexName in indexNames) { var fullChildName = ModelNames.CreateIndexModelName(bindingContext.ModelName, indexName); ModelBindingResult?result; using (bindingContext.EnterNestedScope( elementMetadata, fieldName: indexName, modelName: fullChildName, model: null)) { await ElementBinder.BindModelAsync(bindingContext); result = bindingContext.Result; } var didBind = false; object boundValue = null; 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(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¶meter.index=one¶meter.index=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, }); }
internal async Task <CollectionResult> BindComplexCollectionFromIndexes( ModelBindingContext bindingContext, IEnumerable <string>?indexNames) { bool indexNamesIsFinite; if (indexNames != null) { indexNamesIsFinite = true; } else { indexNamesIsFinite = false; var limit = _maxModelBindingCollectionSize == int.MaxValue ? int.MaxValue : _maxModelBindingCollectionSize + 1; indexNames = Enumerable .Range(0, limit) .Select(i => i.ToString(CultureInfo.InvariantCulture)); } var elementMetadata = bindingContext.ModelMetadata.ElementMetadata !; var boundCollection = new List <TElement?>(); foreach (var indexName in indexNames) { var fullChildName = ModelNames.CreateIndexModelName(bindingContext.ModelName, indexName); ModelBindingResult?result; using (bindingContext.EnterNestedScope( elementMetadata, fieldName: indexName, modelName: fullChildName, model: null)) { await ElementBinder.BindModelAsync(bindingContext); result = bindingContext.Result; } var didBind = false; object?boundValue = null; 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)); } // Did the collection grow larger than the limit? if (boundCollection.Count > _maxModelBindingCollectionSize) { // Look for a non-empty name. Both ModelName and OriginalModelName may be empty at the top level. var name = string.IsNullOrEmpty(bindingContext.ModelName) ? (string.IsNullOrEmpty(bindingContext.OriginalModelName) && bindingContext.ModelMetadata.MetadataKind != ModelMetadataKind.Type ? bindingContext.ModelMetadata.Name : bindingContext.OriginalModelName) : // This name may unfortunately be empty. bindingContext.ModelName; throw new InvalidOperationException(Resources.FormatModelBinding_ExceededMaxModelBindingCollectionSize( name, nameof(MvcOptions), nameof(MvcOptions.MaxModelBindingCollectionSize), _maxModelBindingCollectionSize, bindingContext.ModelMetadata.ElementType)); } return(new CollectionResult(boundCollection) { // If we're working with a fixed set of indexes then this is the format like: // // ?parameter.index=zero¶meter.index=one¶meter.index=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, }); }