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); }); }
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); }
/// <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); }
/// <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; } }
/// <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); } }
/// <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); }