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