public async Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); } var itemName = bindingContext.FieldName; // check if we can get the item from the RouteData var rvd = bindingContext.ActionContext.RouteData.Values; if (rvd.ContainsKey(itemName) && bindingContext.ModelType.IsAssignableFrom(rvd[itemName].GetType())) { bindingContext.Model = rvd[itemName]; if (this.propertyBinders == null || bindingContext.BindingSource != BindingSource.Body) { // we aren't meant to bind request data onto the value from RouteData bindingContext.Result = ModelBindingResult.Success(bindingContext.Model); return; } // Check whether the underlying type of the data is different from the model type if (rvd[itemName].GetType() != bindingContext.ModelType) { // if so recurse on model binding with the underlying model type var servProv = bindingContext.ActionContext.HttpContext.RequestServices; var metadataProvider = servProv.GetRequiredService <IModelMetadataProvider>(); var modelMetadata = metadataProvider.GetMetadataForType(bindingContext.Model.GetType()); bindingContext.ModelMetadata = modelMetadata; var modelBinderFactory = servProv.GetRequiredService <IModelBinderFactory>(); var factoryContext = new ModelBinderFactoryContext() { Metadata = modelMetadata, BindingInfo = new BindingInfo() { BinderModelName = modelMetadata.BinderModelName, BinderType = modelMetadata.BinderType, BindingSource = modelMetadata.BindingSource, PropertyFilterProvider = modelMetadata.PropertyFilterProvider, }, // We're using the model metadata as the cache token here so that TryUpdateModelAsync calls // for the same model type can share a binder. This won't overlap with normal model binding // operations because they use the ParameterDescriptor for the token. CacheToken = modelMetadata }; var underlyingBinder = modelBinderFactory.CreateBinder(factoryContext); await underlyingBinder.BindModelAsync(bindingContext); return; } } if (bindingContext.IsTopLevelObject) { // We know we are editing the data, so we empty all collections, as if there // are no collection items present in the posted data, this means the collection is empty new ContentBindingPreparer().Visit(bindingContext.Model); bindingContext.ModelName = ""; } if (!CanCreateModel(bindingContext)) { return; } //Create model first(if necessary) to avoid reporting errors about properties when activation fails. //if (bindingContext.Model == null) //{ // bindingContext.Model = CreateModel(bindingContext); //} foreach (var property in bindingContext.ModelMetadata.Properties) { if (!CanBindProperty(bindingContext, property)) { continue; } // Pass complex (including collection) values down so that binding system does not unnecessarily // recreate instances or overwrite inner properties that are not bound. No need for this with simple // values because they will be overwritten if binding succeeds. Arrays are never reused because they // cannot be resized. object propertyModel = null; if (property.PropertyGetter != null && property.IsComplexType && !property.ModelType.IsArray) { propertyModel = property.PropertyGetter(bindingContext.Model); } var fieldName = property.BinderModelName ?? property.PropertyName; var modelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, fieldName); ModelBindingResult result; using (bindingContext.EnterNestedScope( modelMetadata: property, fieldName: fieldName, modelName: modelName, model: propertyModel)) { await BindProperty(bindingContext); result = bindingContext.Result; } if (result.IsModelSet) { SetProperty(bindingContext, modelName, property, result); } else if (property.IsBindingRequired) { var message = property.ModelBindingMessageProvider.MissingBindRequiredValueAccessor(fieldName); bindingContext.ModelState.TryAddModelError(modelName, message); } } bindingContext.Result = ModelBindingResult.Success(bindingContext.Model); }
private async Task BindModelCoreAsync(ModelBindingContext bindingContext, ViewConfigure viewConfigure) { if (bindingContext.Model == null) { bindingContext.Model = CreateModel(bindingContext); } for (var i = 0; i < bindingContext.ModelMetadata.Properties.Count; i++) { var property = bindingContext.ModelMetadata.Properties[i]; if (!CanBindProperty(bindingContext, property)) { continue; } object propertyModel = null; if (property.PropertyGetter != null && property.IsComplexType && !property.ModelType.IsArray) { propertyModel = property.PropertyGetter(bindingContext.Model); } var fieldName = property.BinderModelName ?? property.PropertyName; var modelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, fieldName); ModelBindingResult result; using (bindingContext.EnterNestedScope( modelMetadata: property, fieldName: fieldName, modelName: modelName, model: propertyModel)) { await _modelBinderProviderContext.CreateBinder(property).BindModelAsync(bindingContext); result = bindingContext.Result; } if (result.IsModelSet) { SetProperty(bindingContext, modelName, property, result, viewConfigure); } else { var descriptor = viewConfigure.GetViewPortDescriptor(modelName); if (descriptor != null && bindingContext.ModelState.ContainsKey(modelName)) { foreach (var valid in descriptor.Validator) { if (!valid.Validate(bindingContext.ModelState[modelName].RawValue)) { valid.DisplayName = descriptor.DisplayName; bindingContext.ModelState[modelName].Errors.Clear(); bindingContext.ModelState.TryAddModelError(modelName, valid.ErrorMessage); break; } } } else if (property.IsBindingRequired) { var message = property.ModelBindingMessageProvider.MissingBindRequiredValueAccessor(fieldName); bindingContext.ModelState.TryAddModelError(modelName, message); } } } bindingContext.Result = ModelBindingResult.Success(bindingContext.Model); }
// Validates a single node (not including children) // Returns true if validation passes successfully private static bool ShallowValidate( string modelKey, ModelExplorer modelExplorer, ValidationContext validationContext, IList <IModelValidator> validators) { var isValid = true; var modelState = validationContext.ModelValidationContext.ModelState; var fieldValidationState = modelState.GetFieldValidationState(modelKey); if (fieldValidationState == ModelValidationState.Invalid) { // Even if we have no validators it's possible that model binding may have added a // validation error (conversion error, missing data). We want to still run // validators even if that's the case. isValid = false; } // When the are no validators we bail quickly. This saves a GetEnumerator allocation. // In a large array (tens of thousands or more) scenario it's very significant. if (validators == null || validators.Count > 0) { var modelValidationContext = ModelValidationContext.GetChildValidationContext( validationContext.ModelValidationContext, modelExplorer); var modelValidationState = modelState.GetValidationState(modelKey); // If either the model or its properties are unvalidated, validate them now. if (modelValidationState == ModelValidationState.Unvalidated || fieldValidationState == ModelValidationState.Unvalidated) { foreach (var validator in validators) { foreach (var error in validator.Validate(modelValidationContext)) { var errorKey = ModelNames.CreatePropertyModelName(modelKey, error.MemberName); if (!modelState.TryAddModelError(errorKey, error.Message) && modelState.GetFieldValidationState(errorKey) == ModelValidationState.Unvalidated) { // If we are not able to add a model error // for instance when the max error count is reached, mark the model as skipped. modelState.MarkFieldSkipped(errorKey); } isValid = false; } } } } // Add an entry only if there was an entry which was added by a model binder. // This prevents adding spurious entries. if (modelState.ContainsKey(modelKey) && isValid) { validationContext.ModelValidationContext.ModelState.MarkFieldValid(modelKey); } return(isValid); }
private bool CanValueBindAnyModelProperties(ModelBindingContext bindingContext) { // If there are no properties on the model, there is nothing to bind. We are here means this is not a top // level object. So we return false. if (bindingContext.ModelMetadata.Properties.Count == 0) { return(false); } // We want to check to see if any of the properties of the model can be bound using the value providers, // because that's all that MutableObjectModelBinder can handle. // // However, because a property might specify a custom binding source ([FromForm]), it's not correct // for us to just try bindingContext.ValueProvider.ContainsPrefixAsync(bindingContext.ModelName), // because that may include other value providers - that would lead us to mistakenly create the model // when the data is coming from a source we should use (ex: value found in query string, but the // model has [FromForm]). // // To do this we need to enumerate the properties, and see which of them provide a binding source // through metadata, then we decide what to do. // // If a property has a binding source, and it's a greedy source, then it's not // allowed to come from a value provider, so we skip it. // // If a property has a binding source, and it's a non-greedy source, then we'll filter the // the value providers to just that source, and see if we can find a matching prefix // (see CanBindValue). // // If a property does not have a binding source, then it's fair game for any value provider. // // If any property meets the above conditions and has a value from valueproviders, then we'll // create the model and try to bind it. OR if ALL properties of the model have a greedy source, // then we go ahead and create it. // var hasBindableProperty = false; var isAnyPropertyEnabledForValueProviderBasedBinding = false; foreach (var propertyMetadata in bindingContext.ModelMetadata.Properties) { if (!CanBindProperty(bindingContext, propertyMetadata)) { continue; } hasBindableProperty = true; // This check will skip properties which are marked explicitly using a non value binder. var bindingSource = propertyMetadata.BindingSource; if (bindingSource == null || !bindingSource.IsGreedy) { isAnyPropertyEnabledForValueProviderBasedBinding = true; var fieldName = propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName; var modelName = ModelNames.CreatePropertyModelName( bindingContext.ModelName, fieldName); using (bindingContext.EnterNestedScope( modelMetadata: propertyMetadata, fieldName: fieldName, modelName: modelName, model: null)) { // If any property can be bound from a value provider then continue. if (bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName)) { return(true); } } } } if (hasBindableProperty && !isAnyPropertyEnabledForValueProviderBasedBinding) { // All the properties are marked with a non value provider based marker like [FromHeader] or // [FromBody]. return(true); } return(false); }
public async Task BindModelAsync(ModelBindingContext bindingContext) { var constructors = bindingContext.ModelType.GetConstructors(); if (constructors.Length != 1) { throw new InvalidOperationException($"The type '{bindingContext.ModelType}' must have exactly one constructor, but found {constructors.Length}"); } var constructor = constructors[0]; var parameters = constructor.GetParameters(); if (parameters.Length == 0) { throw new InvalidOperationException("the constructor must have at least one parameter, but has 0"); } var propertyCount = bindingContext.ModelType.GetProperties(BindingFlags.Instance | BindingFlags.Public).Length; if (parameters.Length != propertyCount) { throw new InvalidOperationException(FormattableString.Invariant($"The count of the properties ({propertyCount}) and parameters of the constructor ({parameters.Length}) must be the same")); } var allValuesBuilt = true; var modelValues = new Dictionary <string, object>(); foreach (var modelMetadataProperty in bindingContext.ModelMetadata.Properties) { var fieldName = modelMetadataProperty.BinderModelName ?? modelMetadataProperty.PropertyName; var modelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, fieldName); using (bindingContext.EnterNestedScope(modelMetadataProperty, fieldName, modelName, null)) { var modelBinder = _modelBinders[bindingContext.ModelMetadata]; await modelBinder.BindModelAsync(bindingContext); if (bindingContext.Result.IsModelSet) { // the property name is used, because the binder model name could be a different, then the parameter matching logic would not work modelValues.Add(modelMetadataProperty.PropertyName, bindingContext.Result.Model); } else { // if a value for a value type is not present, the creation of an instance would be successful, but the instance would be inconsistent. // Example: // public class Foo { public Foo(int bar){...}...} // invoking the constructor via reflection with null for bar, works. The value of the property is 0. allValuesBuilt = false; break; } } } if (!allValuesBuilt) { bindingContext.Result = ModelBindingResult.Failed(); } else { var parameterValues = new object[parameters.Length]; for (var index = 0; index < parameters.Length; index++) { var parameterInfo = parameters[index]; var(key, value) = modelValues.FirstOrDefault(prop => PnCpComparer.Instance.Equals(prop.Key, parameterInfo.Name !)); Debug.Assert(key != null, "key != null"); parameterValues[index] = value; } var model = constructor.Invoke(parameterValues); bindingContext.Result = ModelBindingResult.Success(model); } }
/// <inheritdoc /> public override async Task <InputFormatterResult> ReadRequestBodyAsync( InputFormatterContext context, Encoding encoding) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (encoding == null) { throw new ArgumentNullException(nameof(encoding)); } var httpContext = context.HttpContext; var request = httpContext.Request; var suppressInputFormatterBuffering = _options.SuppressInputFormatterBuffering; var readStream = request.Body; var disposeReadStream = false; if (readStream.CanSeek) { // The most common way of getting here is the user has request buffering on. // However, request buffering isn't eager, and consequently it will peform pass-thru synchronous // reads as part of the deserialization. // To avoid this, drain and reset the stream. var position = request.Body.Position; await readStream.DrainAsync(CancellationToken.None); readStream.Position = position; } else if (!suppressInputFormatterBuffering) { // JSON.Net does synchronous reads. In order to avoid blocking on the stream, we asynchronously // read everything into a buffer, and then seek back to the beginning. var memoryThreshold = _jsonOptions.InputFormatterMemoryBufferThreshold; var contentLength = request.ContentLength.GetValueOrDefault(); if (contentLength > 0 && contentLength < memoryThreshold) { // If the Content-Length is known and is smaller than the default buffer size, use it. memoryThreshold = (int)contentLength; } readStream = new FileBufferingReadStream(request.Body, memoryThreshold); // Ensure the file buffer stream is always disposed at the end of a request. httpContext.Response.RegisterForDispose(readStream); await readStream.DrainAsync(CancellationToken.None); readStream.Seek(0L, SeekOrigin.Begin); disposeReadStream = true; } var successful = true; Exception?exception = null; object? model; using (var streamReader = context.ReaderFactory(readStream, encoding)) { using var jsonReader = new JsonTextReader(streamReader); jsonReader.ArrayPool = _charPool; jsonReader.CloseInput = false; var type = context.ModelType; var jsonSerializer = CreateJsonSerializer(context); jsonSerializer.Error += ErrorHandler; if (_jsonOptions.ReadJsonWithRequestCulture) { jsonSerializer.Culture = CultureInfo.CurrentCulture; } try { model = jsonSerializer.Deserialize(jsonReader, type); } finally { // Clean up the error handler since CreateJsonSerializer() pools instances. jsonSerializer.Error -= ErrorHandler; ReleaseJsonSerializer(jsonSerializer); if (disposeReadStream) { await readStream.DisposeAsync(); } } } if (successful) { if (model == null && !context.TreatEmptyInputAsDefaultValue) { // Some nonempty inputs might deserialize as null, for example whitespace, // or the JSON-encoded value "null". The upstream BodyModelBinder needs to // be notified that we don't regard this as a real input so it can register // a model binding error. return(InputFormatterResult.NoValue()); } else { return(InputFormatterResult.Success(model)); } } if (exception is not null && exception is not(JsonException or OverflowException or FormatException)) { // At this point we've already recorded all exceptions as an entry in the ModelStateDictionary. // We only need to rethrow an exception if we believe it needs to be handled by something further up // the stack. // JsonException, OverflowException, and FormatException are assumed to be only encountered when // parsing the JSON and are consequently "safe" to be exposed as part of ModelState. Everything else // needs to be rethrown. var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception); exceptionDispatchInfo.Throw(); } return(InputFormatterResult.Failure()); void ErrorHandler(object?sender, Newtonsoft.Json.Serialization.ErrorEventArgs eventArgs) { // Skipping error, if it's already marked as handled // This allows user code to implement its own error handling if (eventArgs.ErrorContext.Handled) { return; } successful = false; // The following addMember logic is intended to append the names of missing required properties to the // ModelStateDictionary key. Normally, just the ModelName and ErrorContext.Path is used for this key, // but ErrorContext.Path does not include the missing required property name like we want it to. // For example, given the following class and input missing the required "Name" property: // // class Person // { // [JsonProperty(Required = Required.Always)] // public string Name { get; set; } // } // // We will see the following ErrorContext: // // Error {"Required property 'Name' not found in JSON. Path 'Person'..."} System.Exception {Newtonsoft.Json.JsonSerializationException} // Member "Name" object {string} // Path "Person" string // // So we update the path used for the ModelStateDictionary key to be "Person.Name" instead of just "Person". // See https://github.com/aspnet/Mvc/issues/8509 var path = eventArgs.ErrorContext.Path; var member = eventArgs.ErrorContext.Member as string; // There are some deserialization exceptions that include the member in the path but not at the end. // For example, given the following classes and invalid input like { "b": { "c": { "d": abc } } }: // // class A // { // public B B { get; set; } // } // class B // { // public C C { get; set; } // } // class C // { // public string D { get; set; } // } // // We will see the following ErrorContext: // // Error {"Unexpected character encountered while parsing value: b. Path 'b.c.d'..."} System.Exception {Newtonsoft.Json.JsonReaderException} // Member "c" object {string} // Path "b.c.d" string // // Notice that Member "c" is in the middle of the Path "b.c.d". The error handler gets invoked for each level of nesting. // null, "b", "c" and "d" are each a Member in different ErrorContexts all reporting the same parsing error. // // The parsing error is reported as a JsonReaderException instead of as a JsonSerializationException like // for missing required properties. We use the exception type to filter out these errors and keep the path used // for the ModelStateDictionary key as "b.c.d" instead of "b.c.d.c" // See https://github.com/dotnet/aspnetcore/issues/33451 var addMember = !string.IsNullOrEmpty(member) && eventArgs.ErrorContext.Error is JsonSerializationException; // There are still JsonSerilizationExceptions that set ErrorContext.Member but include it at the // end of ErrorContext.Path already. The following logic attempts to filter these out. if (addMember) { // Path.Member case (path.Length < member.Length) needs no further checks. if (path.Length == member !.Length) { // Add Member in Path.Member case but not for Path.Path. addMember = !string.Equals(path, member, StringComparison.Ordinal); } else if (path.Length > member.Length) { // Finally, check whether Path already ends or starts with Member. if (member[0] == '[') { addMember = !path.EndsWith(member, StringComparison.Ordinal); } else { addMember = !path.EndsWith($".{member}", StringComparison.Ordinal) && !path.EndsWith($"['{member}']", StringComparison.Ordinal) && !path.EndsWith($"[{member}]", StringComparison.Ordinal); } } } if (addMember) { path = ModelNames.CreatePropertyModelName(path, member); } // Handle path combinations such as ""+"Property", "Parent"+"Property", or "Parent"+"[12]". var key = ModelNames.CreatePropertyModelName(context.ModelName, path); exception = eventArgs.ErrorContext.Error; var metadata = GetPathMetadata(context.Metadata, path); var modelStateException = WrapExceptionForModelState(exception); context.ModelState.TryAddModelError(key, modelStateException, metadata); Log.JsonInputException(_logger, exception); // Error must always be marked as handled // Failure to do so can cause the exception to be rethrown at every recursive level and // overflow the stack for x64 CLR processes eventArgs.ErrorContext.Handled = true; }
private async ValueTask <(bool attemptedBinding, bool bindingSucceeded)> BindPropertiesAsync( ModelBindingContext bindingContext, int propertyData, IReadOnlyList <ModelMetadata> boundProperties) { var attemptedBinding = false; var bindingSucceeded = false; if (boundProperties.Count == 0) { return(attemptedBinding, bindingSucceeded); } var postponePlaceholderBinding = false; for (var i = 0; i < boundProperties.Count; i++) { var property = boundProperties[i]; if (!CanBindItem(bindingContext, property)) { continue; } var propertyBinder = _propertyBinders[property]; if (propertyBinder is PlaceholderBinder) { if (postponePlaceholderBinding) { // Decided to postpone binding properties that complete a loop in the model types when handling // an earlier loop-completing property. Postpone binding this property too. continue; } else if (!bindingContext.IsTopLevelObject && !bindingSucceeded && propertyData == GreedyPropertiesMayHaveData) { // Have no confirmation of data for the current instance. Postpone completing the loop until // we _know_ the current instance is useful. Recursion would otherwise occur prior to the // block with a similar condition after the loop. // // Example cases include an Employee class containing // 1. a Manager property of type Employee // 2. an Employees property of type IList<Employee> postponePlaceholderBinding = true; continue; } } var fieldName = property.BinderModelName ?? property.PropertyName; var modelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, fieldName); var result = await BindPropertyAsync(bindingContext, property, propertyBinder, fieldName, modelName); if (result.IsModelSet) { attemptedBinding = true; bindingSucceeded = true; } else if (property.IsBindingRequired) { attemptedBinding = true; } } if (postponePlaceholderBinding && bindingSucceeded) { // Have some data for this instance. Continue with the model type loop. for (var i = 0; i < boundProperties.Count; i++) { var property = boundProperties[i]; if (!CanBindItem(bindingContext, property)) { continue; } var propertyBinder = _propertyBinders[property]; if (propertyBinder is PlaceholderBinder) { var fieldName = property.BinderModelName ?? property.PropertyName; var modelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, fieldName); await BindPropertyAsync(bindingContext, property, propertyBinder, fieldName, modelName); } } } return(attemptedBinding, bindingSucceeded); }
public async Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); } var postedFiles = new List <IRemoteStreamContent>(); // 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); // If ParameterBinder incorrectly overrode ModelName, fall back to OriginalModelName prefix. Comparisons // are tedious because e.g. top-level parameter or property is named Blah and it contains a BlahBlah // property. OriginalModelName may be null in tests. if (postedFiles.Count == 0 && bindingContext.OriginalModelName != null && !string.Equals(modelName, bindingContext.OriginalModelName, StringComparison.Ordinal) && !modelName.StartsWith(bindingContext.OriginalModelName + "[", StringComparison.Ordinal) && !modelName.StartsWith(bindingContext.OriginalModelName + ".", StringComparison.Ordinal)) { modelName = ModelNames.CreatePropertyModelName(bindingContext.OriginalModelName, modelName); await GetFormFilesAsync(modelName, bindingContext, postedFiles); } object value; if (bindingContext.ModelType == typeof(IRemoteStreamContent) || bindingContext.ModelType == typeof(RemoteStreamContent)) { if (postedFiles.Count == 0) { // Silently fail if the named file does not exist in the request. 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. return; } // Perform any final type mangling needed. var modelType = bindingContext.ModelType; if (modelType == typeof(IRemoteStreamContent[]) || modelType == typeof(RemoteStreamContent[])) { value = postedFiles.ToArray(); } else { value = postedFiles; } } // We need to add a ValidationState entry because the modelName might be non-standard. Otherwise // the entry we create in model state might not be marked as valid. bindingContext.ValidationState.Add(value, new ValidationStateEntry() { Key = modelName, }); bindingContext.ModelState.SetModelValue( modelName, rawValue: null, attemptedValue: null); bindingContext.Result = ModelBindingResult.Success(value); }
private static string GetName(string containerName, ApiParameterDescriptionContext metadata) { var propertyName = !string.IsNullOrEmpty(metadata.BinderModelName) ? metadata.BinderModelName : metadata.PropertyName; return(ModelNames.CreatePropertyModelName(containerName, propertyName)); }
private async Task BindModelCoreAsync(ModelBindingContext bindingContext, int propertyData) { Debug.Assert(propertyData == GreedyPropertiesMayHaveData || propertyData == ValueProviderDataAvailable); // Create model first (if necessary) to avoid reporting errors about properties when activation fails. if (bindingContext.Model == null) { bindingContext.Model = CreateModel(bindingContext); } var modelMetadata = bindingContext.ModelMetadata; var attemptedPropertyBinding = false; var propertyBindingSucceeded = false; var postponePlaceholderBinding = false; for (var i = 0; i < modelMetadata.Properties.Count; i++) { var property = modelMetadata.Properties[i]; if (!CanBindProperty(bindingContext, property)) { continue; } if (_propertyBinders[property] is PlaceholderBinder) { if (postponePlaceholderBinding) { // Decided to postpone binding properties that complete a loop in the model types when handling // an earlier loop-completing property. Postpone binding this property too. continue; } else if (!bindingContext.IsTopLevelObject && !propertyBindingSucceeded && propertyData == GreedyPropertiesMayHaveData) { // Have no confirmation of data for the current instance. Postpone completing the loop until // we _know_ the current instance is useful. Recursion would otherwise occur prior to the // block with a similar condition after the loop. // // Example cases include an Employee class containing // 1. a Manager property of type Employee // 2. an Employees property of type IList<Employee> postponePlaceholderBinding = true; continue; } } var fieldName = property.BinderModelName ?? property.PropertyName; var modelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, fieldName); var result = await BindProperty(bindingContext, property, fieldName, modelName); if (result.IsModelSet) { attemptedPropertyBinding = true; propertyBindingSucceeded = true; } else if (property.IsBindingRequired) { attemptedPropertyBinding = true; } } if (postponePlaceholderBinding && propertyBindingSucceeded) { // Have some data for this instance. Continue with the model type loop. for (var i = 0; i < modelMetadata.Properties.Count; i++) { var property = modelMetadata.Properties[i]; if (!CanBindProperty(bindingContext, property)) { continue; } if (_propertyBinders[property] is PlaceholderBinder) { var fieldName = property.BinderModelName ?? property.PropertyName; var modelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, fieldName); await BindProperty(bindingContext, property, fieldName, modelName); } } } // Have we created a top-level model despite an inability to bind anything in said model and a lack of // other IsBindingRequired errors? Does that violate [BindRequired] on the model? This case occurs when // 1. The top-level model has no public settable properties. // 2. All properties in a [BindRequired] model have [BindNever] or are otherwise excluded from binding. // 3. No data exists for any property. if (!attemptedPropertyBinding && bindingContext.IsTopLevelObject && modelMetadata.IsBindingRequired) { var messageProvider = modelMetadata.ModelBindingMessageProvider; var message = messageProvider.MissingBindRequiredValueAccessor(bindingContext.FieldName); bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, message); } _logger.DoneAttemptingToBindModel(bindingContext); // Have all binders failed because no data was available? // // If CanCreateModel determined a property has data, failures are likely due to conversion errors. For // example, user may submit ?[0].id=twenty&[1].id=twenty-one&[2].id=22 for a collection of a complex type // with an int id property. In that case, the bound model should be [ {}, {}, { id = 22 }] and // ModelState should contain errors about both [0].id and [1].id. Do not inform higher-level binders of the // failure in this and similar cases. // // If CanCreateModel could not find data for non-greedy properties, failures indicate greedy binders were // unsuccessful. For example, user may submit file attachments [0].File and [1].File but not [2].File for // a collection of a complex type containing an IFormFile property. In that case, we have exhausted the // attached files and checking for [3].File is likely be pointless. (And, if it had a point, would we stop // after 10 failures, 100, or more -- all adding redundant errors to ModelState?) Inform higher-level // binders of the failure. // // Required properties do not change the logic below. Missed required properties cause ModelState errors // but do not necessarily prevent further attempts to bind. // // This logic is intended to maximize correctness but does not avoid infinite loops or recursion when a // greedy model binder succeeds unconditionally. if (!bindingContext.IsTopLevelObject && !propertyBindingSucceeded && propertyData == GreedyPropertiesMayHaveData) { bindingContext.Result = ModelBindingResult.Failed(); return; } bindingContext.Result = ModelBindingResult.Success(bindingContext.Model); }
/// <inheritdoc /> public override async Task <InputFormatterResult> ReadRequestBodyAsync( InputFormatterContext context, Encoding encoding) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (encoding == null) { throw new ArgumentNullException(nameof(encoding)); } var request = context.HttpContext.Request; var suppressInputFormatterBuffering = _options.SuppressInputFormatterBuffering; if (!request.Body.CanSeek && !suppressInputFormatterBuffering) { // JSON.Net does synchronous reads. In order to avoid blocking on the stream, we asynchronously // read everything into a buffer, and then seek back to the beginning. request.EnableBuffering(); Debug.Assert(request.Body.CanSeek); await request.Body.DrainAsync(CancellationToken.None); request.Body.Seek(0L, SeekOrigin.Begin); } using (var streamReader = context.ReaderFactory(request.Body, encoding)) { using (var jsonReader = new JsonTextReader(streamReader)) { jsonReader.ArrayPool = _charPool; jsonReader.CloseInput = false; var successful = true; Exception exception = null; void ErrorHandler(object sender, Newtonsoft.Json.Serialization.ErrorEventArgs eventArgs) { successful = false; // When ErrorContext.Path does not include ErrorContext.Member, add Member to form full path. var path = eventArgs.ErrorContext.Path; var member = eventArgs.ErrorContext.Member?.ToString(); var addMember = !string.IsNullOrEmpty(member); if (addMember) { // Path.Member case (path.Length < member.Length) needs no further checks. if (path.Length == member.Length) { // Add Member in Path.Memb case but not for Path.Path. addMember = !string.Equals(path, member, StringComparison.Ordinal); } else if (path.Length > member.Length) { // Finally, check whether Path already ends with Member. if (member[0] == '[') { addMember = !path.EndsWith(member, StringComparison.Ordinal); } else { addMember = !path.EndsWith("." + member, StringComparison.Ordinal); } } } if (addMember) { path = ModelNames.CreatePropertyModelName(path, member); } // Handle path combinations such as ""+"Property", "Parent"+"Property", or "Parent"+"[12]". var key = ModelNames.CreatePropertyModelName(context.ModelName, path); exception = eventArgs.ErrorContext.Error; var metadata = GetPathMetadata(context.Metadata, path); var modelStateException = WrapExceptionForModelState(exception); context.ModelState.TryAddModelError(key, modelStateException, metadata); _logger.JsonInputException(exception); // Error must always be marked as handled // Failure to do so can cause the exception to be rethrown at every recursive level and // overflow the stack for x64 CLR processes eventArgs.ErrorContext.Handled = true; } var type = context.ModelType; var jsonSerializer = CreateJsonSerializer(); jsonSerializer.Error += ErrorHandler; object model; try { model = jsonSerializer.Deserialize(jsonReader, type); } finally { // Clean up the error handler since CreateJsonSerializer() pools instances. jsonSerializer.Error -= ErrorHandler; ReleaseJsonSerializer(jsonSerializer); } if (successful) { if (model == null && !context.TreatEmptyInputAsDefaultValue) { // Some nonempty inputs might deserialize as null, for example whitespace, // or the JSON-encoded value "null". The upstream BodyModelBinder needs to // be notified that we don't regard this as a real input so it can register // a model binding error. return(InputFormatterResult.NoValue()); } else { return(InputFormatterResult.Success(model)); } } if (!(exception is JsonException || exception is OverflowException)) { var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception); exceptionDispatchInfo.Throw(); } return(InputFormatterResult.Failure()); } } }
/// <inheritdoc cref="IModelBinder.BindModelAsync(ModelBindingContext)" /> public async Task BindModelAsync(ModelBindingContext bindingContext) { if (!CanCreateModel(bindingContext)) { return; } if (bindingContext.Model == null) { bindingContext.Model = CreateModel(bindingContext); } var modelMetadata = bindingContext.ModelMetadata; var attemptedPropertyBinding = false; for (var i = 0; i < modelMetadata.Properties.Count; i++) { var property = modelMetadata.Properties[i]; var dataMemberAttribute = ((DefaultModelMetadata)property).Attributes.Attributes.OfType <DataMemberAttribute>().FirstOrDefault(); if (!CanBindProperty(bindingContext, property)) { continue; } object propertyModel = null; if (property.PropertyGetter != null && property.IsComplexType && !property.ModelType.IsArray) { propertyModel = property.PropertyGetter(bindingContext.Model); } var fieldName = property.BinderModelName ?? property.PropertyName; var modelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, fieldName); ModelBindingResult result; using (bindingContext.EnterNestedScope(property, fieldName, modelName, propertyModel)) { var innerBindinContext = new DefaultModelBindingContext { ModelMetadata = property, ModelName = dataMemberAttribute.Name, ModelState = bindingContext.ModelState, FieldName = dataMemberAttribute.Name, ValueProvider = bindingContext.ValueProvider }; await BindProperty(innerBindinContext); result = innerBindinContext.Result; } if (result.IsModelSet) { attemptedPropertyBinding = true; SetProperty(bindingContext, modelName, property, result); } else if (property.IsBindingRequired) { attemptedPropertyBinding = true; var message = property.ModelBindingMessageProvider.MissingBindRequiredValueAccessor(fieldName); bindingContext.ModelState.TryAddModelError(modelName, message); } } if (!attemptedPropertyBinding && bindingContext.IsTopLevelObject && modelMetadata.IsBindingRequired) { var messageProvider = modelMetadata.ModelBindingMessageProvider; var message = messageProvider.MissingBindRequiredValueAccessor(bindingContext.FieldName); bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, message); } bindingContext.Result = ModelBindingResult.Success(bindingContext.Model); }
public override async Task<InputFormatterResult> ReadRequestBodyAsync( InputFormatterContext context, Encoding encoding) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (encoding == null) { throw new ArgumentNullException(nameof(encoding)); } var request = context.HttpContext.Request; var suppressInputFormatterBuffering = options?.SuppressInputFormatterBuffering ?? false; if (!request.Body.CanSeek && !suppressInputFormatterBuffering) { // JSON.Net does synchronous reads. In order to avoid blocking on the stream, we asynchronously // read everything into a buffer, and then seek back to the beginning. request.EnableBuffering(); Debug.Assert(request.Body.CanSeek); await request.Body.DrainAsync(CancellationToken.None); request.Body.Seek(0L, SeekOrigin.Begin); } using (var streamReader = context.ReaderFactory(request.Body, encoding)) { using (var jsonReader = new JsonTextReader(streamReader)) { jsonReader.ArrayPool = charPool; jsonReader.CloseInput = false; var successful = true; Exception exception = null; void ErrorHandler(object sender, Newtonsoft.Json.Serialization.ErrorEventArgs eventArgs) { successful = false; var path = eventArgs.ErrorContext.Path; var key = ModelNames.CreatePropertyModelName(context.ModelName, path); context.ModelState.TryAddModelError(key, $"Invalid value specified for {path}"); eventArgs.ErrorContext.Handled = true; } var type = context.ModelType; var jsonSerializer = CreateJsonSerializer(); jsonSerializer.Error += ErrorHandler; object model; try { model = jsonSerializer.Deserialize(jsonReader, type); } finally { // Clean up the error handler since CreateJsonSerializer() pools instances. jsonSerializer.Error -= ErrorHandler; ReleaseJsonSerializer(jsonSerializer); } if (successful) { if (model == null && !context.TreatEmptyInputAsDefaultValue) { // Some nonempty inputs might deserialize as null, for example whitespace, // or the JSON-encoded value "null". The upstream BodyModelBinder needs to // be notified that we don't regard this as a real input so it can register // a model binding error. return InputFormatterResult.NoValue(); } else { return InputFormatterResult.Success(model); } } if (!(exception is JsonException || exception is OverflowException)) { var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception); exceptionDispatchInfo.Throw(); } return InputFormatterResult.Failure(); } } }
/// <inheritdoc /> public override async Task <InputFormatterResult> ReadRequestBodyAsync( InputFormatterContext context, Encoding encoding) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (encoding == null) { throw new ArgumentNullException(nameof(encoding)); } var httpContext = context.HttpContext; var request = httpContext.Request; var suppressInputFormatterBuffering = _options.SuppressInputFormatterBuffering; var readStream = request.Body; var disposeReadStream = false; if (readStream.CanSeek) { // The most common way of getting here is the user has request buffering on. // However, request buffering isn't eager, and consequently it will peform pass-thru synchronous // reads as part of the deserialization. // To avoid this, drain and reset the stream. var position = request.Body.Position; await readStream.DrainAsync(CancellationToken.None); readStream.Position = position; } else if (!suppressInputFormatterBuffering) { // JSON.Net does synchronous reads. In order to avoid blocking on the stream, we asynchronously // read everything into a buffer, and then seek back to the beginning. var memoryThreshold = _jsonOptions.InputFormatterMemoryBufferThreshold; var contentLength = request.ContentLength.GetValueOrDefault(); if (contentLength > 0 && contentLength < memoryThreshold) { // If the Content-Length is known and is smaller than the default buffer size, use it. memoryThreshold = (int)contentLength; } readStream = new FileBufferingReadStream(request.Body, memoryThreshold); // Ensure the file buffer stream is always disposed at the end of a request. httpContext.Response.RegisterForDispose(readStream); await readStream.DrainAsync(CancellationToken.None); readStream.Seek(0L, SeekOrigin.Begin); disposeReadStream = true; } var successful = true; Exception exception = null; object model; using (var streamReader = context.ReaderFactory(readStream, encoding)) { using var jsonReader = new JsonTextReader(streamReader); jsonReader.ArrayPool = _charPool; jsonReader.CloseInput = false; var type = context.ModelType; var jsonSerializer = CreateJsonSerializer(context); jsonSerializer.Error += ErrorHandler; try { model = jsonSerializer.Deserialize(jsonReader, type); } finally { // Clean up the error handler since CreateJsonSerializer() pools instances. jsonSerializer.Error -= ErrorHandler; ReleaseJsonSerializer(jsonSerializer); if (disposeReadStream) { await readStream.DisposeAsync(); } } } if (successful) { if (model == null && !context.TreatEmptyInputAsDefaultValue) { // Some nonempty inputs might deserialize as null, for example whitespace, // or the JSON-encoded value "null". The upstream BodyModelBinder needs to // be notified that we don't regard this as a real input so it can register // a model binding error. return(InputFormatterResult.NoValue()); } else { return(InputFormatterResult.Success(model)); } } if (!(exception is JsonException || exception is OverflowException || exception is FormatException)) { // At this point we've already recorded all exceptions as an entry in the ModelStateDictionary. // We only need to rethrow an exception if we believe it needs to be handled by something further up // the stack. // JsonException, OverflowException, and FormatException are assumed to be only encountered when // parsing the JSON and are consequently "safe" to be exposed as part of ModelState. Everything else // needs to be rethrown. var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception); exceptionDispatchInfo.Throw(); } return(InputFormatterResult.Failure()); void ErrorHandler(object sender, Newtonsoft.Json.Serialization.ErrorEventArgs eventArgs) { successful = false; // When ErrorContext.Path does not include ErrorContext.Member, add Member to form full path. var path = eventArgs.ErrorContext.Path; var member = eventArgs.ErrorContext.Member?.ToString(); var addMember = !string.IsNullOrEmpty(member); if (addMember) { // Path.Member case (path.Length < member.Length) needs no further checks. if (path.Length == member.Length) { // Add Member in Path.Memb case but not for Path.Path. addMember = !string.Equals(path, member, StringComparison.Ordinal); } else if (path.Length > member.Length) { // Finally, check whether Path already ends with Member. if (member[0] == '[') { addMember = !path.EndsWith(member, StringComparison.Ordinal); } else { addMember = !path.EndsWith("." + member, StringComparison.Ordinal) && !path.EndsWith("['" + member + "']", StringComparison.Ordinal) && !path.EndsWith("[" + member + "]", StringComparison.Ordinal); } } } if (addMember) { path = ModelNames.CreatePropertyModelName(path, member); } // Handle path combinations such as ""+"Property", "Parent"+"Property", or "Parent"+"[12]". var key = ModelNames.CreatePropertyModelName(context.ModelName, path); exception = eventArgs.ErrorContext.Error; var metadata = GetPathMetadata(context.Metadata, path); var modelStateException = WrapExceptionForModelState(exception); context.ModelState.TryAddModelError(key, modelStateException, metadata); _logger.JsonInputException(exception); // Error must always be marked as handled // Failure to do so can cause the exception to be rethrown at every recursive level and // overflow the stack for x64 CLR processes eventArgs.ErrorContext.Handled = true; } }
private int CanBindAnyModelItem(ModelBindingContext bindingContext) { // If there are no properties on the model, and no constructor parameters, there is nothing to bind. We are here means this is not a top // level object. So we return false. var modelMetadata = bindingContext.ModelMetadata; var performsConstructorBinding = bindingContext.Model == null && modelMetadata.BoundConstructor != null; if (modelMetadata.Properties.Count == 0 && (!performsConstructorBinding || modelMetadata.BoundConstructor.BoundConstructorParameters.Count == 0)) { Log.NoPublicSettableItems(_logger, bindingContext); return(NoDataAvailable); } // We want to check to see if any of the properties of the model can be bound using the value providers or // a greedy binder. // // Because a property might specify a custom binding source ([FromForm]), it's not correct // for us to just try bindingContext.ValueProvider.ContainsPrefixAsync(bindingContext.ModelName); // that may include other value providers - that would lead us to mistakenly create the model // when the data is coming from a source we should use (ex: value found in query string, but the // model has [FromForm]). // // To do this we need to enumerate the properties, and see which of them provide a binding source // through metadata, then we decide what to do. // // If a property has a binding source, and it's a greedy source, then it's always bound. // // If a property has a binding source, and it's a non-greedy source, then we'll filter the // the value providers to just that source, and see if we can find a matching prefix // (see CanBindValue). // // If a property does not have a binding source, then it's fair game for any value provider. // // Bottom line, if any property meets the above conditions and has a value from ValueProviders, then we'll // create the model and try to bind it. Of, if ANY properties of the model have a greedy source, // then we go ahead and create it. var hasGreedyBinders = false; for (var i = 0; i < bindingContext.ModelMetadata.Properties.Count; i++) { var propertyMetadata = bindingContext.ModelMetadata.Properties[i]; if (!CanBindItem(bindingContext, propertyMetadata)) { continue; } // If any property can be bound from a greedy binding source, then success. var bindingSource = propertyMetadata.BindingSource; if (bindingSource != null && bindingSource.IsGreedy) { hasGreedyBinders = true; continue; } // Otherwise, check whether the (perhaps filtered) value providers have a match. var fieldName = propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName; var modelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, fieldName); using (bindingContext.EnterNestedScope( modelMetadata: propertyMetadata, fieldName: fieldName, modelName: modelName, model: null)) { // If any property can be bound from a value provider, then success. if (bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName)) { return(ValueProviderDataAvailable); } } } if (performsConstructorBinding) { var parameters = bindingContext.ModelMetadata.BoundConstructor.BoundConstructorParameters; for (var i = 0; i < parameters.Count; i++) { var parameterMetadata = parameters[i]; if (!CanBindItem(bindingContext, parameterMetadata)) { continue; } // If any parameter can be bound from a greedy binding source, then success. var bindingSource = parameterMetadata.BindingSource; if (bindingSource != null && bindingSource.IsGreedy) { hasGreedyBinders = true; continue; } // Otherwise, check whether the (perhaps filtered) value providers have a match. var fieldName = parameterMetadata.BinderModelName ?? parameterMetadata.ParameterName; var modelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, fieldName); using (bindingContext.EnterNestedScope( modelMetadata: parameterMetadata, fieldName: fieldName, modelName: modelName, model: null)) { // If any parameter can be bound from a value provider, then success. if (bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName)) { return(ValueProviderDataAvailable); } } } } if (hasGreedyBinders) { return(GreedyPropertiesMayHaveData); } _logger.CannotBindToComplexType(bindingContext); return(NoDataAvailable); }
/// <inheritdoc /> public override async Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); } await base.BindModelAsync(bindingContext); if (!bindingContext.Result.IsModelSet) { // No match for the prefix at all. return; } var result = bindingContext.Result; Debug.Assert(result.Model != null); var model = (IDictionary <TKey, TValue>)result.Model; if (model.Count != 0) { // ICollection<KeyValuePair<TKey, TValue>> approach was successful. return; } Logger.NoKeyValueFormatForDictionaryModelBinder(bindingContext); if (!(bindingContext.ValueProvider is IEnumerableValueProvider enumerableValueProvider)) { // No IEnumerableValueProvider available for the fallback approach. For example the user may have // replaced the ValueProvider with something other than a CompositeValueProvider. if (bindingContext.IsTopLevelObject) { AddErrorIfBindingRequired(bindingContext); } return; } // Attempt to bind dictionary from a set of prefix[key]=value entries. Get the short and long keys first. var prefix = bindingContext.ModelName; var keys = enumerableValueProvider.GetKeysFromPrefix(prefix); if (keys.Count == 0) { // No entries with the expected keys. if (bindingContext.IsTopLevelObject) { AddErrorIfBindingRequired(bindingContext); } return; } // Update the existing successful but empty ModelBindingResult. var elementMetadata = bindingContext.ModelMetadata.ElementMetadata; var valueMetadata = elementMetadata.Properties[nameof(KeyValuePair <TKey, TValue> .Value)]; var keyMappings = new Dictionary <string, TKey>(StringComparer.Ordinal); foreach (var kvp in keys) { // Use InvariantCulture to convert the key since ExpressionHelper.GetExpressionText() would use // that culture when rendering a form. var convertedKey = ModelBindingHelper.ConvertTo <TKey>(kvp.Key, culture: null); using (bindingContext.EnterNestedScope( modelMetadata: valueMetadata, fieldName: bindingContext.FieldName, modelName: kvp.Value, model: null)) { await _valueBinder.BindModelAsync(bindingContext); var valueResult = bindingContext.Result; if (!valueResult.IsModelSet) { // Factories for IKeyRewriterValueProvider implementations are not all-or-nothing i.e. // "[key][propertyName]" may be rewritten as ".key.propertyName" or "[key].propertyName". Try // again in case this scope is binding a complex type and rewriting // landed on ".key.propertyName" or in case this scope is binding another collection and an // IKeyRewriterValueProvider implementation was first (hiding the original "[key][next key]"). if (kvp.Value.EndsWith("]")) { bindingContext.ModelName = ModelNames.CreatePropertyModelName(prefix, kvp.Key); } else { bindingContext.ModelName = ModelNames.CreateIndexModelName(prefix, kvp.Key); } await _valueBinder.BindModelAsync(bindingContext); valueResult = bindingContext.Result; } // Always add an entry to the dictionary but validate only if binding was successful. model[convertedKey] = ModelBindingHelper.CastOrDefault <TValue>(valueResult.Model); keyMappings.Add(bindingContext.ModelName, convertedKey); } } bindingContext.ValidationState.Add(model, new ValidationStateEntry() { Strategy = new ShortFormDictionaryValidationStrategy <TKey, TValue>(keyMappings, valueMetadata), }); }
/// <summary> /// Validates a single node in a model object graph. /// </summary> /// <returns><c>true</c> if the node is valid, otherwise <c>false</c>.</returns> protected virtual bool ValidateNode() { var state = ModelState.GetValidationState(Key); // Rationale: we might see the same model state key used for two different objects. // We want to run validation unless it's already known that this key is invalid. if (state != ModelValidationState.Invalid) { var validators = Cache.GetValidators(Metadata, ValidatorProvider); var count = validators.Count; if (count > 0) { var context = new ModelValidationContext( Context, Metadata, MetadataProvider, Container, Model); var results = new List <ModelValidationResult>(); for (var i = 0; i < count; i++) { results.AddRange(validators[i].Validate(context)); } var resultsCount = results.Count; for (var i = 0; i < resultsCount; i++) { var result = results[i]; var key = ModelNames.CreatePropertyModelName(Key, result.MemberName); // If this is a top-level parameter/property, the key would be empty, // so use the name of the top-level property if (string.IsNullOrEmpty(key) && Metadata.PropertyName != null) { key = Metadata.PropertyName; } ModelState.TryAddModelError(key, result.Message); } } } state = ModelState.GetFieldValidationState(Key); if (state == ModelValidationState.Invalid) { return(false); } else { // If the field has an entry in ModelState, then record it as valid. Don't create // extra entries if they don't exist already. var entry = ModelState[Key]; if (entry != null) { entry.ValidationState = ModelValidationState.Valid; } return(true); } }
protected override bool ValidateNode() { // private field workaround... var _key = _keyGetter(this); var _metadata = _metadataGetter(this); var _container = _containerGetter(this); var _model = _modelGetter(this); // This method is dupliated from MVC's VlaidationVisitor.cs // They only validate if validationstate != invalid, but if there's a FV validator, we want to override this behaviour. var fluentValidator = _validatorFactory.GetValidator(_metadata.ModelType); var state = _modelState.GetValidationState(_key); // Rationale: we might see the same model state key used for two different objects. // We want to run validation unless it's already known that this key is invalid. if (state != ModelValidationState.Invalid || fluentValidator != null) { var validators = _validatorCache.GetValidators(_metadata, _validatorProvider).ToList(); if (fluentValidator != null) { validators.Add(new FluentValidationModelValidator(fluentValidator, _cutomizations)); } var count = validators.Count; if (count > 0) { var context = new ModelValidationContext( _actionContext, _metadata, _metadataProvider, _container, _model); var results = new List <ModelValidationResult>(); for (var i = 0; i < count; i++) { results.AddRange(validators[i].Validate(context)); } var resultsCount = results.Count; for (var i = 0; i < resultsCount; i++) { var result = results[i]; var key = ModelNames.CreatePropertyModelName(_key, result.MemberName); _modelState.TryAddModelError(key, result.Message); } } } state = _modelState.GetFieldValidationState(_key); if (state == ModelValidationState.Invalid) { return(false); } else { // If the field has an entry in ModelState, then record it as valid. Don't create // extra entries if they don't exist already. var entry = _modelState[_key]; if (entry != null) { entry.ValidationState = ModelValidationState.Valid; } return(true); } }
private async Task BindModelCoreAsync(ModelBindingContext bindingContext) { // Create model first (if necessary) to avoid reporting errors about properties when activation fails. if (bindingContext.Model == null) { bindingContext.Model = CreateModel(bindingContext); } var modelMetadata = bindingContext.ModelMetadata; var attemptedPropertyBinding = false; for (var i = 0; i < modelMetadata.Properties.Count; i++) { var property = modelMetadata.Properties[i]; if (!CanBindProperty(bindingContext, property)) { continue; } // Pass complex (including collection) values down so that binding system does not unnecessarily // recreate instances or overwrite inner properties that are not bound. No need for this with simple // values because they will be overwritten if binding succeeds. Arrays are never reused because they // cannot be resized. object propertyModel = null; if (property.PropertyGetter != null && property.IsComplexType && !property.ModelType.IsArray) { propertyModel = property.PropertyGetter(bindingContext.Model); } var fieldName = property.BinderModelName ?? property.PropertyName; var modelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, fieldName); ModelBindingResult result; using (bindingContext.EnterNestedScope( modelMetadata: property, fieldName: fieldName, modelName: modelName, model: propertyModel)) { await BindProperty(bindingContext); result = bindingContext.Result; } if (result.IsModelSet) { attemptedPropertyBinding = true; SetProperty(bindingContext, modelName, property, result); } else if (property.IsBindingRequired) { attemptedPropertyBinding = true; var message = property.ModelBindingMessageProvider.MissingBindRequiredValueAccessor(fieldName); bindingContext.ModelState.TryAddModelError(modelName, message); } } // Have we created a top-level model despite an inability to bind anything in said model and a lack of // other IsBindingRequired errors? Does that violate [BindRequired] on the model? This case occurs when // 1. The top-level model has no public settable properties. // 2. All properties in a [BindRequired] model have [BindNever] or are otherwise excluded from binding. // 3. No data exists for any property. if (AllowValidatingTopLevelNodes && !attemptedPropertyBinding && bindingContext.IsTopLevelObject && modelMetadata.IsBindingRequired) { var messageProvider = modelMetadata.ModelBindingMessageProvider; var message = messageProvider.MissingBindRequiredValueAccessor(bindingContext.FieldName); bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, message); } bindingContext.Result = ModelBindingResult.Success(bindingContext.Model); _logger.DoneAttemptingToBindModel(bindingContext); }