/// <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));
        }
Beispiel #3
0
        /// <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&parameter[zero]=0&&parameter[one]=1&parameter[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,
            });
        }