Example #1
0
        public async Task BindModelAsync_EnforcesTopLevelDataAnnotationsAttribute()
        {
            // Arrange
            var actionContext       = GetControllerContext();
            var mockModelMetadata   = CreateMockModelMetadata();
            var validationAttribute = new RangeAttribute(1, 100);

            mockModelMetadata.Setup(o => o.DisplayName).Returns("My Display Name");
            mockModelMetadata.Setup(o => o.ValidatorMetadata).Returns(new[] {
                validationAttribute
            });

            var validator = new DataAnnotationsModelValidator(
                new ValidationAttributeAdapterProvider(),
                validationAttribute,
                stringLocalizer: null);

            var parameterBinder    = CreateParameterBinder(mockModelMetadata.Object, validator);
            var modelBindingResult = ModelBindingResult.Success(123);

            // Act
            var result = await parameterBinder.BindModelAsync(
                actionContext,
                CreateMockModelBinder(modelBindingResult),
                CreateMockValueProvider(),
                new ParameterDescriptor { Name = "myParam", ParameterType = typeof(Person) },
                mockModelMetadata.Object,
                50); // This value is ignored, because test explicitly set the ModelBindingResult

            // Assert
            Assert.False(actionContext.ModelState.IsValid);
            Assert.Equal("myParam", actionContext.ModelState.Single().Key);
            Assert.Equal(
                validationAttribute.FormatErrorMessage("My Display Name"),
                actionContext.ModelState.Single().Value.Errors.Single().ErrorMessage);
        }
Example #2
0
        public async Task BindModelAsync_DoesNotEnforceTopLevelRequired_IfNotValidatingTopLevelNodes()
        {
            // Arrange
            var actionContext     = GetControllerContext();
            var mockModelMetadata = CreateMockModelMetadata();

            mockModelMetadata.Setup(o => o.IsRequired).Returns(true);
            mockModelMetadata.Setup(o => o.DisplayName).Returns("My Display Name");
            mockModelMetadata.Setup(o => o.ValidatorMetadata).Returns(new[]
            {
                new RequiredAttribute()
            });

            var validator = new DataAnnotationsModelValidator(
                new ValidationAttributeAdapterProvider(),
                new RequiredAttribute(),
                stringLocalizer: null);

            // Do not set AllowValidatingTopLevelNodes.
            var optionsAccessor    = Options.Create(new MvcOptions());
            var parameterBinder    = CreateParameterBinder(mockModelMetadata.Object, validator, optionsAccessor);
            var modelBindingResult = ModelBindingResult.Success(null);

            // Act
            var result = await parameterBinder.BindModelAsync(
                actionContext,
                CreateMockModelBinder(modelBindingResult),
                CreateMockValueProvider(),
                new ParameterDescriptor { Name = "myParam", ParameterType = typeof(Person) },
                mockModelMetadata.Object,
                "ignoredvalue");

            // Assert
            Assert.True(actionContext.ModelState.IsValid);
            Assert.Empty(actionContext.ModelState);
        }
        /// <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);
        }
Example #4
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);
        }
        /// <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;
            }
        }
        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);
            }
        }
        /// <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);
        }
        /// <inheritdoc/>
        public async Task BindModelAsync(ModelBindingContext bindingContext)
        {
            Check.NotNull(bindingContext, nameof(bindingContext));

            _logger.AttemptingToBindModel(bindingContext);

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

            if (valueProviderResult == ValueProviderResult.None)
            {
                _logger.FoundNoValueInRequest(bindingContext);

                // no entry
                _logger.DoneAttemptingToBindModel(bindingContext);
                return;
            }

            var modelState = bindingContext.ModelState;

            modelState.SetModelValue(modelName, valueProviderResult);

            var metadata = bindingContext.ModelMetadata;
            var type     = metadata.UnderlyingOrModelType;

            try
            {
                var value = valueProviderResult.FirstValue;

                object?model;
                if (value is null)
                {
                    model = null;
                }
                else if (type == typeof(DateTimeOffset))
                {
                    if (DateTimeOffset.TryParse(value, valueProviderResult.Culture, DateTimeStyles.None | DateTimeStyles.AllowWhiteSpaces, out var actValue))
                    {
                        model = actValue;
                    }
                    else
                    {
                        model = null;
                    }

                    if (_dateTimeOffsetBinderOptions.HasValue && long.TryParse(value, out var timestamp))
                    {
                        switch (_dateTimeOffsetBinderOptions)
                        {
                        case DateTimeOffsetBinderOptions.SupportSeconds:
                            model = DateTimeOffset.FromUnixTimeSeconds(timestamp);
                            break;

                        case DateTimeOffsetBinderOptions.SupportMilliseconds:
                            model = DateTimeOffset.FromUnixTimeMilliseconds(timestamp);
                            break;

                        default:
                            model = null;
                            break;
                        }
                    }
                }
                else
                {
                    throw new NotSupportedException();
                }

                if (model == null && !metadata.IsReferenceOrNullableType)
                {
                    modelState.TryAddModelError(
                        modelName,
                        metadata.ModelBindingMessageProvider.ValueMustNotBeNullAccessor(
                            valueProviderResult.ToString()));
                }
                else
                {
                    bindingContext.Result = ModelBindingResult.Success(model);
                }
            }
            catch (Exception exception)
            {
                modelState.TryAddModelError(modelName, exception, metadata);
            }

            _logger.DoneAttemptingToBindModel(bindingContext);
            await Task.CompletedTask;
        }