/// <inheritdoc /> public Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); } if (bindingContext.ModelType == typeof(CancellationToken)) { // We need to force boxing now, so we can insert the same reference to the boxed CancellationToken // in both the ValidationState and ModelBindingResult. // // DO NOT simplify this code by removing the cast. var model = (object)bindingContext.OperationBindingContext.HttpContext.RequestAborted; bindingContext.ValidationState.Add(model, new ValidationStateEntry() { SuppressValidation = true }); bindingContext.Result = ModelBindingResult.Success(bindingContext.ModelName, model); } return(TaskCache.CompletedTask); }
public async Task BindModelAsync_EnforcesTopLevelRequired() { // 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); var parameterBinder = CreateParameterBinder(mockModelMetadata.Object, validator); 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.False(actionContext.ModelState.IsValid); Assert.Equal("myParam", actionContext.ModelState.Single().Key); Assert.Equal( new RequiredAttribute().FormatErrorMessage("My Display Name"), actionContext.ModelState.Single().Value.Errors.Single().ErrorMessage); }
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); }
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); }); }
/// <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; } }
private void EnforceBindRequiredAndValidate(ActionContext actionContext, ModelMetadata metadata, ModelBindingContext modelBindingContext, ModelBindingResult modelBindingResult) { if (!modelBindingResult.IsModelSet && metadata.IsBindingRequired) { // Enforce BindingBehavior.Required (e.g., [BindRequired]) var modelName = modelBindingContext.FieldName; var message = metadata.ModelBindingMessageProvider.MissingBindRequiredValueAccessor(modelName); actionContext.ModelState.TryAddModelError(modelName, message); } else if (modelBindingResult.IsModelSet || metadata.IsRequired) { // Enforce any other validation rules var visitor = new ValidationVisitor( actionContext, _validatorProvider, _validatorCache, _modelMetadataProvider, modelBindingContext.ValidationState); visitor.Validate( metadata, modelBindingContext.ModelName, modelBindingResult.Model, alwaysValidateAtTopLevel: metadata.IsRequired); } }
/// <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); } }
/// <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; }