/// <summary> /// Attempts to convert the values in <paramref name="result"/> to the specified type. /// </summary> /// <typeparam name="T">The <see cref="Type"/> for conversion.</typeparam> /// <param name="result">The <see cref="ValueProviderResult"/>.</param> /// <returns> /// The converted value, or the default value of <typeparamref name="T"/> if the value could not be converted. /// </returns> public static T ConvertTo <T>(this ValueProviderResult result) { object valueToConvert = null; if (result.Values.Count == 1) { valueToConvert = result.Values[0]; } else if (result.Values.Count > 1) { valueToConvert = result.Values.ToArray(); } return(ModelBindingHelper.ConvertTo <T>(valueToConvert, result.Culture)); }
/// <summary> /// Attempts to convert the values in <paramref name="result"/> to the specified type. /// </summary> /// <param name="result">The <see cref="ValueProviderResult"/>.</param> /// <param name="type">The <see cref="Type"/> for conversion.</param> /// <returns> /// The converted value, or the default value of <paramref name="type"/> if the value could not be converted. /// </returns> public static object ConvertTo(this ValueProviderResult result, Type type) { if (type == null) { throw new ArgumentNullException(nameof(type)); } object valueToConvert = null; if (result.Values.Count == 1) { valueToConvert = result.Values[0]; } else if (result.Values.Count > 1) { valueToConvert = result.Values.ToArray(); } return(ModelBindingHelper.ConvertTo(valueToConvert, type, result.Culture)); }
/// <inheritdoc /> public Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); } // This method is optimized to use cached tasks when possible and avoid allocating // using Task.FromResult or async state machines. var modelType = bindingContext.ModelType; if (modelType != typeof(IFormFile) && !typeof(IEnumerable <IFormFile>).IsAssignableFrom(modelType)) { // Not a type this model binder supports. Let other binders run. return(TaskCache.CompletedTask); } var createFileCollection = modelType == typeof(IFormFileCollection) && !bindingContext.ModelMetadata.IsReadOnly; if (!createFileCollection && !ModelBindingHelper.CanGetCompatibleCollection <IFormFile>(bindingContext)) { // Silently fail and stop other model binders running if unable to create an instance or use the // current instance. bindingContext.Result = ModelBindingResult.Failed(bindingContext.ModelName); return(TaskCache.CompletedTask); } ICollection <IFormFile> postedFiles; if (createFileCollection) { postedFiles = new List <IFormFile>(); } else { postedFiles = ModelBindingHelper.GetCompatibleCollection <IFormFile>(bindingContext); } return(BindModelCoreAsync(bindingContext, postedFiles)); }
// 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 }); }
/// <inheritdoc /> public Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); } ModelBindingHelper.ValidateBindingContext(bindingContext); if (!CanBindType(bindingContext.ModelMetadata)) { return(TaskCache.CompletedTask); } if (!CanCreateModel(bindingContext)) { return(TaskCache.CompletedTask); } // Perf: separated to avoid allocating a state machine when we don't // need to go async. return(BindModelCoreAsync(bindingContext)); }
private static object GetCompatibleCollection(ModelBindingContext bindingContext, string[] values) { // Almost-always success if IsTopLevelObject. if (!bindingContext.IsTopLevelObject && values.Length == 0) { return(null); } if (bindingContext.ModelType.IsAssignableFrom(typeof(string[]))) { // Array we already have is compatible. return(values); } var collection = ModelBindingHelper.GetCompatibleCollection <string>(bindingContext, values.Length); for (int i = 0; i < values.Length; i++) { collection.Add(values[i]); } return(collection); }
/// <inheritdoc /> public Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); } // This method is optimized to use cached tasks when possible and avoid allocating // using Task.FromResult or async state machines. var allowedBindingSource = bindingContext.BindingSource; if (allowedBindingSource == null || !allowedBindingSource.CanAcceptDataFrom(BindingSource.Header)) { // Headers are opt-in. This model either didn't specify [FromHeader] or specified something // incompatible so let other binders run. return(TaskCache.CompletedTask); } var request = bindingContext.OperationBindingContext.HttpContext.Request; // Property name can be null if the model metadata represents a type (rather than a property or parameter). var headerName = bindingContext.FieldName; object model; if (ModelBindingHelper.CanGetCompatibleCollection <string>(bindingContext)) { if (bindingContext.ModelType == typeof(string)) { var value = request.Headers[headerName]; model = (string)value; } else { var values = request.Headers.GetCommaSeparatedValues(headerName); model = GetCompatibleCollection(bindingContext, values); } } else { // An unsupported datatype or a new collection is needed (perhaps because target type is an array) but // can't assign it to the property. model = null; } if (model == null) { // Silently fail if unable to create an instance or use the current instance. Also reach here in the // typeof(string) case if the header does not exist in the request and in the // typeof(IEnumerable<string>) case if the header does not exist and this is not a top-level object. bindingContext.Result = ModelBindingResult.Failed(bindingContext.ModelName); } else { bindingContext.ModelState.SetModelValue( bindingContext.ModelName, request.Headers.GetCommaSeparatedValues(headerName), request.Headers[headerName]); bindingContext.Result = ModelBindingResult.Success(bindingContext.ModelName, model); } return(TaskCache.CompletedTask); }
/// <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), }); }
/// <inheritdoc /> public virtual async Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); } ModelBindingHelper.ValidateBindingContext(bindingContext); var model = bindingContext.Model; if (!bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName)) { // If we failed to find data for a top-level model, then generate a // default 'empty' model (or use existing Model) and return it. if (bindingContext.IsTopLevelObject) { if (model == null) { model = CreateEmptyCollection(bindingContext.ModelType); } bindingContext.Result = ModelBindingResult.Success(bindingContext.ModelName, model); } return; } var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); CollectionResult result; if (valueProviderResult == ValueProviderResult.None) { result = await BindComplexCollection(bindingContext); } else { result = await BindSimpleCollection(bindingContext, valueProviderResult); } var boundCollection = result.Model; if (model == null) { model = ConvertToCollectionType(bindingContext.ModelType, boundCollection); } else { // Special case for TryUpdateModelAsync(collection, ...) scenarios. Model is null in all other cases. CopyToModel(model, boundCollection); } Debug.Assert(model != null); if (result.ValidationStrategy != null) { bindingContext.ValidationState.Add(model, new ValidationStateEntry() { Strategy = result.ValidationStrategy, }); } if (valueProviderResult != ValueProviderResult.None) { // If we did simple binding, then modelstate should be updated to reflect what we bound for ModelName. // If we did complex binding, there will already be an entry for each index. bindingContext.ModelState.SetModelValue( bindingContext.ModelName, valueProviderResult); } bindingContext.Result = ModelBindingResult.Success(bindingContext.ModelName, model); }
// 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, }); }