Esempio n. 1
0
        public async Task BindModelAsync_ForOverlappingParametersWithSuppressions_InValid_WithInValidSecondParameter()
        {
            // Arrange
            var parameterDescriptor = new ParameterDescriptor
            {
                Name          = "patchDocument",
                ParameterType = typeof(IJsonPatchDocument),
            };

            var actionContext = GetControllerContext();
            var modelState    = actionContext.ModelState;

            // First ModelState key is not empty to match SimpleTypeModelBinder.
            modelState.SetModelValue("id", "notAGuid", "notAGuid");
            modelState.AddModelError("id", "This is not valid.");

            // Second ModelState key is empty to match BodyModelBinder.
            modelState.AddModelError(string.Empty, "This is also not valid.");

            var modelMetadataProvider = new TestModelMetadataProvider();

            modelMetadataProvider.ForType <IJsonPatchDocument>().ValidationDetails(v => v.ValidateChildren = false);
            var modelMetadata = modelMetadataProvider.GetMetadataForType(typeof(IJsonPatchDocument));

            var parameterBinder = new ParameterBinder(
                modelMetadataProvider,
                Mock.Of <IModelBinderFactory>(),
                new DefaultObjectValidator(
                    modelMetadataProvider,
                    new[] { TestModelValidatorProvider.CreateDefaultProvider() },
                    new MvcOptions()),
                _optionsAccessor,
                NullLoggerFactory.Instance);

            var modelBindingResult = ModelBindingResult.Failed();
            var modelBinder        = CreateMockModelBinder(modelBindingResult);

            // Act
            var result = await parameterBinder.BindModelAsync(
                actionContext,
                modelBinder,
                new SimpleValueProvider(),
                parameterDescriptor,
                modelMetadata,
                value : null);

            // Assert
            Assert.False(result.IsModelSet);
            Assert.False(modelState.IsValid);
            Assert.Collection(
                modelState,
                kvp =>
            {
                Assert.Empty(kvp.Key);
                Assert.Equal(ModelValidationState.Invalid, kvp.Value.ValidationState);
                var error = Assert.Single(kvp.Value.Errors);
                Assert.Equal("This is also not valid.", error.ErrorMessage);
            },
                kvp =>
            {
                Assert.Equal("id", kvp.Key);
                Assert.Equal(ModelValidationState.Invalid, kvp.Value.ValidationState);
                var error = Assert.Single(kvp.Value.Errors);
                Assert.Equal("This is not valid.", error.ErrorMessage);
            });
        }
Esempio n. 2
0
        private async Task BindModelCoreAsync(ModelBindingContext bindingContext, ICollection <IFormFile> postedFiles)
        {
            Debug.Assert(postedFiles != null);

            // If we're at the top level, then use the FieldName (parameter or property name).
            // This handles the fact that there will be nothing in the ValueProviders for this parameter
            // and so we'll do the right thing even though we 'fell-back' to the empty prefix.
            var modelName = bindingContext.IsTopLevelObject
                ? bindingContext.BinderModelName ?? bindingContext.FieldName
                : bindingContext.ModelName;

            await GetFormFilesAsync(modelName, bindingContext, postedFiles);

            object value;

            if (bindingContext.ModelType == typeof(IFormFile))
            {
                if (postedFiles.Count == 0)
                {
                    // Silently fail if the named file does not exist in the request.
                    bindingContext.Result = ModelBindingResult.Failed(bindingContext.ModelName);
                    return;
                }

                value = postedFiles.First();
            }
            else
            {
                if (postedFiles.Count == 0 && !bindingContext.IsTopLevelObject)
                {
                    // Silently fail if no files match. Will bind to an empty collection (treat empty as a success
                    // case and not reach here) if binding to a top-level object.
                    bindingContext.Result = ModelBindingResult.Failed(bindingContext.ModelName);
                    return;
                }

                // Perform any final type mangling needed.
                var modelType = bindingContext.ModelType;
                if (modelType == typeof(IFormFile[]))
                {
                    Debug.Assert(postedFiles is List <IFormFile>);
                    value = ((List <IFormFile>)postedFiles).ToArray();
                }
                else if (modelType == typeof(IFormFileCollection))
                {
                    Debug.Assert(postedFiles is List <IFormFile>);
                    value = new FileCollection((List <IFormFile>)postedFiles);
                }
                else
                {
                    value = postedFiles;
                }
            }

            bindingContext.ValidationState.Add(value, new ValidationStateEntry()
            {
                Key = modelName,
                SuppressValidation = true
            });

            bindingContext.ModelState.SetModelValue(
                modelName,
                rawValue: null,
                attemptedValue: null);

            bindingContext.Result = ModelBindingResult.Success(bindingContext.ModelName, value);
        }
        /// <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);
        }
Esempio n. 4
0
        /// <summary>
        /// Binds a model specified by <paramref name="parameter"/> using <paramref name="value"/> as the initial value.
        /// </summary>
        /// <param name="actionContext">The <see cref="ActionContext"/>.</param>
        /// <param name="modelBinder">The <see cref="IModelBinder"/>.</param>
        /// <param name="valueProvider">The <see cref="IValueProvider"/>.</param>
        /// <param name="parameter">The <see cref="ParameterDescriptor"/></param>
        /// <param name="metadata">The <see cref="ModelMetadata"/>.</param>
        /// <param name="value">The initial model value.</param>
        /// <returns>The result of model binding.</returns>
        public virtual async Task <ModelBindingResult> BindModelAsync(
            ActionContext actionContext,
            IModelBinder modelBinder,
            IValueProvider valueProvider,
            ParameterDescriptor parameter,
            ModelMetadata metadata,
            object value)
        {
            if (actionContext == null)
            {
                throw new ArgumentNullException(nameof(actionContext));
            }

            if (modelBinder == null)
            {
                throw new ArgumentNullException(nameof(modelBinder));
            }

            if (valueProvider == null)
            {
                throw new ArgumentNullException(nameof(valueProvider));
            }

            if (parameter == null)
            {
                throw new ArgumentNullException(nameof(parameter));
            }

            if (metadata == null)
            {
                throw new ArgumentNullException(nameof(metadata));
            }

            if (parameter.BindingInfo?.RequestPredicate?.Invoke(actionContext) == false)
            {
                return(ModelBindingResult.Failed());
            }

            var modelBindingContext = DefaultModelBindingContext.CreateBindingContext(
                actionContext,
                valueProvider,
                metadata,
                parameter.BindingInfo,
                parameter.Name);

            modelBindingContext.Model = value;

            Logger.AttemptingToBindParameterOrProperty(parameter, modelBindingContext);

            var parameterModelName = parameter.BindingInfo?.BinderModelName ?? metadata.BinderModelName;

            if (parameterModelName != null)
            {
                // The name was set explicitly, always use that as the prefix.
                modelBindingContext.ModelName = parameterModelName;
            }
            else if (modelBindingContext.ValueProvider.ContainsPrefix(parameter.Name))
            {
                // We have a match for the parameter name, use that as that prefix.
                modelBindingContext.ModelName = parameter.Name;
            }
            else
            {
                // No match, fallback to empty string as the prefix.
                modelBindingContext.ModelName = string.Empty;
            }

            await modelBinder.BindModelAsync(modelBindingContext);

            Logger.DoneAttemptingToBindParameterOrProperty(parameter, modelBindingContext);

            var modelBindingResult = modelBindingContext.Result;

            if (_mvcOptions.AllowValidatingTopLevelNodes &&
                _objectModelValidator is ObjectModelValidator baseObjectValidator)
            {
                Logger.AttemptingToValidateParameterOrProperty(parameter, modelBindingContext);

                EnforceBindRequiredAndValidate(
                    baseObjectValidator,
                    actionContext,
                    parameter,
                    metadata,
                    modelBindingContext,
                    modelBindingResult);

                Logger.DoneAttemptingToValidateParameterOrProperty(parameter, modelBindingContext);
            }
            else
            {
                // For legacy implementations (which directly implemented IObjectModelValidator), fall back to the
                // back-compatibility logic. In this scenario, top-level validation attributes will be ignored like
                // they were historically.
                if (modelBindingResult.IsModelSet)
                {
                    _objectModelValidator.Validate(
                        actionContext,
                        modelBindingContext.ValidationState,
                        modelBindingContext.ModelName,
                        modelBindingResult.Model);
                }
            }

            return(modelBindingResult);
        }
Esempio n. 5
0
        /// <summary>
        /// Attempts to bind the model using formatters.
        /// </summary>
        /// <param name="bindingContext">The <see cref="ModelBindingContext"/>.</param>
        /// <returns>
        /// A <see cref="Task{ModelBindingResult}"/> which when completed returns a <see cref="ModelBindingResult"/>.
        /// </returns>
        private async Task BindModelCoreAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext == null)
            {
                throw new ArgumentNullException(nameof(bindingContext));
            }

            // Special logic for body, treat the model name as string.Empty for the top level
            // object, but allow an override via BinderModelName. The purpose of this is to try
            // and be similar to the behavior for POCOs bound via traditional model binding.
            string modelBindingKey;

            if (bindingContext.IsTopLevelObject)
            {
                modelBindingKey = bindingContext.BinderModelName ?? string.Empty;
            }
            else
            {
                modelBindingKey = bindingContext.ModelName;
            }

            var httpContext = bindingContext.OperationBindingContext.HttpContext;

            var formatterContext = new InputFormatterContext(
                httpContext,
                modelBindingKey,
                bindingContext.ModelState,
                bindingContext.ModelMetadata,
                _readerFactory);

            var formatters = bindingContext.OperationBindingContext.InputFormatters;
            var formatter  = formatters.FirstOrDefault(f => f.CanRead(formatterContext));

            if (formatter == null)
            {
                var message = Resources.FormatUnsupportedContentType(
                    bindingContext.OperationBindingContext.HttpContext.Request.ContentType);

                var exception = new UnsupportedContentTypeException(message);
                bindingContext.ModelState.AddModelError(modelBindingKey, exception, bindingContext.ModelMetadata);

                // This model binder is the only handler for the Body binding source and it cannot run twice. Always
                // tell the model binding system to skip other model binders and never to fall back i.e. indicate a
                // fatal error.
                bindingContext.Result = ModelBindingResult.Failed(modelBindingKey);
                return;
            }

            try
            {
                var previousCount = bindingContext.ModelState.ErrorCount;
                var result        = await formatter.ReadAsync(formatterContext);

                var model = result.Model;

                if (result.HasError)
                {
                    // Formatter encountered an error. Do not use the model it returned. As above, tell the model
                    // binding system to skip other model binders and never to fall back.
                    bindingContext.Result = ModelBindingResult.Failed(modelBindingKey);
                    return;
                }

                bindingContext.Result = ModelBindingResult.Success(modelBindingKey, model);
                return;
            }
            catch (Exception ex)
            {
                bindingContext.ModelState.AddModelError(modelBindingKey, ex, bindingContext.ModelMetadata);

                // This model binder is the only handler for the Body binding source and it cannot run twice. Always
                // tell the model binding system to skip other model binders and never to fall back i.e. indicate a
                // fatal error.
                bindingContext.Result = ModelBindingResult.Failed(modelBindingKey);
                return;
            }
        }
Esempio n. 6
0
        /// <summary>
        /// Binds a model specified by <paramref name="parameter"/> using <paramref name="value"/> as the initial value.
        /// </summary>
        /// <param name="actionContext">The <see cref="ActionContext"/>.</param>
        /// <param name="modelBinder">The <see cref="IModelBinder"/>.</param>
        /// <param name="valueProvider">The <see cref="IValueProvider"/>.</param>
        /// <param name="parameter">The <see cref="ParameterDescriptor"/></param>
        /// <param name="metadata">The <see cref="ModelMetadata"/>.</param>
        /// <param name="value">The initial model value.</param>
        /// <returns>The result of model binding.</returns>
        public virtual async Task <ModelBindingResult> BindModelAsync(
            ActionContext actionContext,
            IModelBinder modelBinder,
            IValueProvider valueProvider,
            ParameterDescriptor parameter,
            ModelMetadata metadata,
            object value)
        {
            if (actionContext == null)
            {
                throw new ArgumentNullException(nameof(actionContext));
            }

            if (modelBinder == null)
            {
                throw new ArgumentNullException(nameof(modelBinder));
            }

            if (valueProvider == null)
            {
                throw new ArgumentNullException(nameof(valueProvider));
            }

            if (parameter == null)
            {
                throw new ArgumentNullException(nameof(parameter));
            }

            if (metadata == null)
            {
                throw new ArgumentNullException(nameof(metadata));
            }

            if (parameter.BindingInfo?.RequestPredicate?.Invoke(actionContext) == false)
            {
                return(ModelBindingResult.Failed());
            }

            var modelBindingContext = DefaultModelBindingContext.CreateBindingContext(
                actionContext,
                valueProvider,
                metadata,
                parameter.BindingInfo,
                parameter.Name);

            modelBindingContext.Model = value;

            var parameterModelName = parameter.BindingInfo?.BinderModelName ?? metadata.BinderModelName;

            if (parameterModelName != null)
            {
                // The name was set explicitly, always use that as the prefix.
                modelBindingContext.ModelName = parameterModelName;
            }
            else if (modelBindingContext.ValueProvider.ContainsPrefix(parameter.Name))
            {
                // We have a match for the parameter name, use that as that prefix.
                modelBindingContext.ModelName = parameter.Name;
            }
            else
            {
                // No match, fallback to empty string as the prefix.
                modelBindingContext.ModelName = string.Empty;
            }

            await modelBinder.BindModelAsync(modelBindingContext);

            var modelBindingResult = modelBindingContext.Result;

            if (_validatorForBackCompatOnly != null)
            {
                // Since we don't have access to an IModelValidatorProvider, fall back
                // on back-compatibility logic. In this scenario, top-level validation
                // attributes will be ignored like they were historically.
                if (modelBindingResult.IsModelSet)
                {
                    _validatorForBackCompatOnly.Validate(
                        actionContext,
                        modelBindingContext.ValidationState,
                        modelBindingContext.ModelName,
                        modelBindingResult.Model);
                }
            }
            else
            {
                EnforceBindRequiredAndValidate(
                    actionContext,
                    metadata,
                    modelBindingContext,
                    modelBindingResult);
            }

            return(modelBindingResult);
        }
        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. If you need to make changes of this nature, profile
            // allocations afterwards and look for Task<ModelBindingResult>.

            if (bindingContext.ModelMetadata.IsComplexType)
            {
                // this type cannot be converted
                return(TaskCache.CompletedTask);
            }

            var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

            if (valueProviderResult == ValueProviderResult.None)
            {
                // no entry
                return(TaskCache.CompletedTask);
            }

            bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);

            try
            {
                var model = valueProviderResult.ConvertTo(bindingContext.ModelType);

                if (bindingContext.ModelType == typeof(string))
                {
                    var modelAsString = model as string;
                    if (bindingContext.ModelMetadata.ConvertEmptyStringToNull &&
                        string.IsNullOrEmpty(modelAsString))
                    {
                        model = null;
                    }
                }

                // When converting newModel a null value may indicate a failed conversion for an otherwise required
                // model (can't set a ValueType to null). This detects if a null model value is acceptable given the
                // current bindingContext. If not, an error is logged.
                if (model == null && !bindingContext.ModelMetadata.IsReferenceOrNullableType)
                {
                    bindingContext.ModelState.TryAddModelError(
                        bindingContext.ModelName,
                        bindingContext.ModelMetadata.ModelBindingMessageProvider.ValueMustNotBeNullAccessor(
                            valueProviderResult.ToString()));

                    bindingContext.Result = ModelBindingResult.Failed(bindingContext.ModelName);
                    return(TaskCache.CompletedTask);
                }
                else
                {
                    bindingContext.Result = ModelBindingResult.Success(bindingContext.ModelName, model);
                    return(TaskCache.CompletedTask);
                }
            }
            catch (Exception exception)
            {
                bindingContext.ModelState.TryAddModelError(
                    bindingContext.ModelName,
                    exception,
                    bindingContext.ModelMetadata);

                // Were able to find a converter for the type but conversion failed.
                // Tell the model binding system to skip other model binders.
                bindingContext.Result = ModelBindingResult.Failed(bindingContext.ModelName);
                return(TaskCache.CompletedTask);
            }
        }
Esempio n. 8
0
        /// <summary>
        /// Binds a model specified by <paramref name="parameter"/> using <paramref name="value"/> as the initial value.
        /// </summary>
        /// <param name="actionContext">The <see cref="ActionContext"/>.</param>
        /// <param name="modelBinder">The <see cref="IModelBinder"/>.</param>
        /// <param name="valueProvider">The <see cref="IValueProvider"/>.</param>
        /// <param name="parameter">The <see cref="ParameterDescriptor"/></param>
        /// <param name="metadata">The <see cref="ModelMetadata"/>.</param>
        /// <param name="value">The initial model value.</param>
        /// <returns>The result of model binding.</returns>
        public virtual async Task <ModelBindingResult> BindModelAsync(
            ActionContext actionContext,
            IModelBinder modelBinder,
            IValueProvider valueProvider,
            ParameterDescriptor parameter,
            ModelMetadata metadata,
            object value)
        {
            if (actionContext == null)
            {
                throw new ArgumentNullException(nameof(actionContext));
            }

            if (modelBinder == null)
            {
                throw new ArgumentNullException(nameof(modelBinder));
            }

            if (valueProvider == null)
            {
                throw new ArgumentNullException(nameof(valueProvider));
            }

            if (parameter == null)
            {
                throw new ArgumentNullException(nameof(parameter));
            }

            if (metadata == null)
            {
                throw new ArgumentNullException(nameof(metadata));
            }

            if (parameter.BindingInfo?.RequestPredicate?.Invoke(actionContext) == false)
            {
                return(ModelBindingResult.Failed());
            }

            var modelBindingContext = DefaultModelBindingContext.CreateBindingContext(
                actionContext,
                valueProvider,
                metadata,
                parameter.BindingInfo,
                parameter.Name);

            modelBindingContext.Model = value;

            var parameterModelName = parameter.BindingInfo?.BinderModelName ?? metadata.BinderModelName;

            if (parameterModelName != null)
            {
                // The name was set explicitly, always use that as the prefix.
                modelBindingContext.ModelName = parameterModelName;
            }
            else if (modelBindingContext.ValueProvider.ContainsPrefix(parameter.Name))
            {
                // We have a match for the parameter name, use that as that prefix.
                modelBindingContext.ModelName = parameter.Name;
            }
            else
            {
                // No match, fallback to empty string as the prefix.
                modelBindingContext.ModelName = string.Empty;
            }

            await modelBinder.BindModelAsync(modelBindingContext);

            var modelBindingResult = modelBindingContext.Result;

            if (modelBindingResult.IsModelSet)
            {
                _validator.Validate(
                    actionContext,
                    modelBindingContext.ValidationState,
                    modelBindingContext.ModelName,
                    modelBindingResult.Model);
            }

            return(modelBindingResult);
        }