internal async Task <ModelBindingResult> TryBindStrongModel <TModel>(ModelBindingContext parentBindingContext,
                                                                             string propertyName)
        {
            var propertyModelMetadata =
                parentBindingContext.OperationBindingContext.MetadataProvider.GetMetadataForType(typeof(TModel));
            var propertyModelName =
                ModelBindingHelper.CreatePropertyModelName(parentBindingContext.ModelName, propertyName);
            var propertyBindingContext = ModelBindingContext.GetChildModelBindingContext(
                parentBindingContext,
                propertyModelName,
                propertyModelMetadata);

            propertyBindingContext.BinderModelName = propertyModelMetadata.BinderModelName;

            var modelBindingResult = await propertyBindingContext.OperationBindingContext.ModelBinder.BindModelAsync(
                propertyBindingContext);

            if (modelBindingResult != null)
            {
                return(modelBindingResult);
            }

            // Always return a ModelBindingResult to avoid an NRE in BindModelAsync.
            return(new ModelBindingResult(model: default(TModel), key: propertyModelName, isModelSet: false));
        }
Exemplo n.º 2
0
        private void ValidateProperties(ModelValidationContext validationContext)
        {
            var modelState = validationContext.ModelState;

            var model           = ModelMetadata.Model;
            var updatedMetadata = validationContext.MetadataProvider.GetMetadataForType(() => model,
                                                                                        ModelMetadata.ModelType);

            foreach (var propertyMetadata in updatedMetadata.Properties)
            {
                // Only want to add errors to ModelState if something doesn't already exist for the property node,
                // else we could end up with duplicate or irrelevant error messages.
                var propertyKeyRoot = ModelBindingHelper.CreatePropertyModelName(ModelStateKey,
                                                                                 propertyMetadata.PropertyName);

                if (modelState.GetFieldValidationState(propertyKeyRoot) == ModelValidationState.Unvalidated)
                {
                    var propertyValidators        = GetValidators(validationContext, propertyMetadata);
                    var propertyValidationContext = new ModelValidationContext(validationContext, propertyMetadata);
                    foreach (var propertyValidator in propertyValidators)
                    {
                        foreach (var propertyResult in propertyValidator.Validate(propertyValidationContext))
                        {
                            var thisErrorKey = ModelBindingHelper.CreatePropertyModelName(propertyKeyRoot,
                                                                                          propertyResult.MemberName);
                            modelState.AddModelError(thisErrorKey, propertyResult.Message);
                        }
                    }
                }
            }
        }
Exemplo n.º 3
0
        private async Task <bool> CanBindValue(ModelBindingContext bindingContext, ModelMetadata metadata)
        {
            var valueProvider         = bindingContext.ValueProvider;
            var valueProviderMetadata = metadata.BinderMetadata as IValueProviderMetadata;

            if (valueProviderMetadata != null)
            {
                // if there is a binder metadata and since the property can be bound using a value provider.
                var metadataAwareValueProvider =
                    bindingContext.OperationBindingContext.ValueProvider as IMetadataAwareValueProvider;
                if (metadataAwareValueProvider != null)
                {
                    valueProvider = metadataAwareValueProvider.Filter(valueProviderMetadata);
                }
            }

            var propertyModelName = ModelBindingHelper.CreatePropertyModelName(bindingContext.ModelName,
                                                                               metadata.PropertyName);

            if (await valueProvider.ContainsPrefixAsync(propertyModelName))
            {
                return(true);
            }

            return(false);
        }
Exemplo n.º 4
0
        public async Task <bool> BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext.ModelType == typeof(ComplexModelDto))
            {
                ModelBindingHelper.ValidateBindingContext(bindingContext,
                                                          typeof(ComplexModelDto),
                                                          allowNullModel: false);

                var dto = (ComplexModelDto)bindingContext.Model;
                foreach (var propertyMetadata in dto.PropertyMetadata)
                {
                    var propertyModelName = ModelBindingHelper.CreatePropertyModelName(bindingContext.ModelName,
                                                                                       propertyMetadata.PropertyName);

                    var propertyBindingContext = new ModelBindingContext(bindingContext,
                                                                         propertyModelName,
                                                                         propertyMetadata);

                    // bind and propagate the values
                    // If we can't bind then leave the result missing (don't add a null).
                    if (await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(propertyBindingContext))
                    {
                        var result = ComplexModelDtoResult.FromBindingContext(propertyBindingContext);
                        dto.Results[propertyMetadata] = result;
                    }
                }

                return(true);
            }

            return(false);
        }
Exemplo n.º 5
0
        internal void ProcessDto(ModelBindingContext bindingContext, ComplexModelDto dto)
        {
            var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider;
            var modelExplorer    = metadataProvider.GetModelExplorerForType(bindingContext.ModelType, bindingContext.Model);

            var validationInfo = GetPropertyValidationInfo(bindingContext);

            // Eliminate provided properties from requiredProperties; leaving just *missing* required properties.
            var boundProperties = dto.Results.Where(p => p.Value.IsModelSet).Select(p => p.Key.PropertyName);

            validationInfo.RequiredProperties.ExceptWith(boundProperties);

            foreach (var missingRequiredProperty in validationInfo.RequiredProperties)
            {
                var addedError = false;

                // We want to provide the 'null' value, not the value of model.Property,
                // so avoiding modelExplorer.GetProperty here which would call the actual getter on the
                // model. This avoids issues with value types, or properties with pre-initialized values.
                var propertyExplorer = modelExplorer.GetExplorerForProperty(missingRequiredProperty, model: null);

                var propertyName  = propertyExplorer.Metadata.BinderModelName ?? missingRequiredProperty;
                var modelStateKey = ModelBindingHelper.CreatePropertyModelName(
                    bindingContext.ModelName,
                    propertyName);

                // Execute validator (if any) to get custom error message.
                IModelValidator validator;
                if (validationInfo.RequiredValidators.TryGetValue(missingRequiredProperty, out validator))
                {
                    addedError = RunValidator(validator, bindingContext, propertyExplorer, modelStateKey);
                }

                // Fall back to default message if BindingBehaviorAttribute required this property or validator
                // (oddly) succeeded.
                if (!addedError)
                {
                    bindingContext.ModelState.TryAddModelError(
                        modelStateKey,
                        Resources.FormatMissingRequiredMember(propertyName));
                }
            }

            // For each property that ComplexModelDtoModelBinder attempted to bind, call the setter, recording
            // exceptions as necessary.
            foreach (var entry in dto.Results)
            {
                var dtoResult = entry.Value;
                if (dtoResult != null)
                {
                    var             propertyMetadata = entry.Key;
                    IModelValidator requiredValidator;
                    validationInfo.RequiredValidators.TryGetValue(
                        propertyMetadata.PropertyName,
                        out requiredValidator);

                    SetProperty(bindingContext, modelExplorer, propertyMetadata, dtoResult, requiredValidator);
                }
            }
        }
Exemplo n.º 6
0
        private void ValidateThis(ModelValidationContext validationContext, ModelValidationNode parentNode)
        {
            var modelState = validationContext.ModelState;

            if (modelState.GetFieldValidationState(ModelStateKey) == ModelValidationState.Invalid)
            {
                // If any item in the key's subtree has been identified as invalid, short-circuit
                return;
            }

            // If the Model at the current node is null and there is no parent, we cannot validate, and the
            // DataAnnotationsModelValidator will throw. So we intercept here to provide a catch-all value-required
            // validation error
            if (parentNode == null && ModelMetadata.Model == null)
            {
                modelState.TryAddModelError(ModelStateKey, Resources.Validation_ValueNotFound);
                return;
            }

            var container  = TryConvertContainerToMetadataType(parentNode);
            var validators = GetValidators(validationContext, ModelMetadata).ToArray();

            for (var i = 0; i < validators.Length; i++)
            {
                var validator = validators[i];
                foreach (var validationResult in validator.Validate(validationContext))
                {
                    var currentModelStateKey = ModelBindingHelper.CreatePropertyModelName(ModelStateKey,
                                                                                          validationResult.MemberName);
                    modelState.TryAddModelError(currentModelStateKey, validationResult.Message);
                }
            }
        }
Exemplo n.º 7
0
        // Used when the ValueProvider contains the collection to be bound as multiple elements, e.g. foo[0], foo[1].
        private async Task <List <TElement> > BindComplexCollection(ModelBindingContext bindingContext)
        {
            var indexPropertyName        = ModelBindingHelper.CreatePropertyModelName(bindingContext.ModelName, "index");
            var valueProviderResultIndex = await bindingContext.ValueProvider.GetValueAsync(indexPropertyName);

            var indexNames = CollectionModelBinderUtil.GetIndexNamesFromValueProviderResult(valueProviderResultIndex);

            return(await BindComplexCollectionFromIndexes(bindingContext, indexNames));
        }
Exemplo n.º 8
0
        internal void ProcessDto(ModelBindingContext bindingContext, ComplexModelDto dto)
        {
            var validationInfo = GetPropertyValidationInfo(bindingContext);

            // Eliminate provided properties from requiredProperties; leaving just *missing* required properties.
            var boundProperties = dto.Results.Where(p => p.Value.IsModelBound).Select(p => p.Key.PropertyName);

            validationInfo.RequiredProperties.ExceptWith(boundProperties);

            foreach (var missingRequiredProperty in validationInfo.RequiredProperties)
            {
                var addedError    = false;
                var modelStateKey = ModelBindingHelper.CreatePropertyModelName(
                    bindingContext.ValidationNode.ModelStateKey, missingRequiredProperty);

                // Update Model as SetProperty() would: Place null value where validator will check for non-null. This
                // ensures a failure result from a required validator (if any) even for a non-nullable property.
                // (Otherwise, propertyMetadata.Model is likely already null.)
                var propertyMetadata = bindingContext.PropertyMetadata[missingRequiredProperty];
                propertyMetadata.Model = null;

                // Execute validator (if any) to get custom error message.
                IModelValidator validator;
                if (validationInfo.RequiredValidators.TryGetValue(missingRequiredProperty, out validator))
                {
                    addedError = RunValidator(validator, bindingContext, propertyMetadata, modelStateKey);
                }

                // Fall back to default message if BindingBehaviorAttribute required this property or validator
                // (oddly) succeeded.
                if (!addedError)
                {
                    bindingContext.ModelState.TryAddModelError(
                        modelStateKey,
                        Resources.FormatMissingRequiredMember(missingRequiredProperty));
                }
            }

            // for each property that was bound, call the setter, recording exceptions as necessary
            foreach (var entry in dto.Results)
            {
                var propertyMetadata = entry.Key;
                var dtoResult        = entry.Value;
                if (dtoResult != null)
                {
                    IModelValidator requiredValidator;
                    validationInfo.RequiredValidators.TryGetValue(propertyMetadata.PropertyName,
                                                                  out requiredValidator);
                    SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator);
                    bindingContext.ValidationNode.ChildNodes.Add(dtoResult.ValidationNode);
                }
            }
        }
Exemplo n.º 9
0
        // Validates a single node (not including children)
        // Returns true if validation passes successfully
        private static bool ShallowValidate(
            ModelMetadata metadata,
            ValidationContext validationContext,
            [NotNull] IEnumerable <IModelValidator> validators)
        {
            var    isValid  = true;
            string modelKey = null;

            // 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.
            var validatorsAsCollection = validators as ICollection;

            if (validatorsAsCollection != null && validatorsAsCollection.Count == 0)
            {
                return(isValid);
            }

            var modelValidationContext =
                new ModelValidationContext(validationContext.ModelValidationContext, metadata);

            foreach (var validator in validators)
            {
                foreach (var error in validator.Validate(modelValidationContext))
                {
                    if (modelKey == null)
                    {
                        modelKey = validationContext.RootPrefix;
                        // This constructs the object heirarchy
                        // Example: prefix.Parent.Child
                        foreach (var keyBuilder in validationContext.KeyBuilders.Reverse())
                        {
                            modelKey = keyBuilder.AppendTo(modelKey);
                        }
                    }

                    var errorKey = ModelBindingHelper.CreatePropertyModelName(modelKey, error.MemberName);
                    validationContext.ModelValidationContext.ModelState.AddModelError(errorKey, error.Message);
                    isValid = false;
                }
            }

            return(isValid);
        }
Exemplo n.º 10
0
        internal async Task <BindResult <TModel> > TryBindStrongModel <TModel>(ModelBindingContext parentBindingContext,
                                                                               string propertyName)
        {
            var propertyBindingContext = new ModelBindingContext(parentBindingContext)
            {
                ModelMetadata = parentBindingContext.MetadataProvider.GetMetadataForType(modelAccessor: null,
                                                                                         modelType: typeof(TModel)),
                ModelName = ModelBindingHelper.CreatePropertyModelName(parentBindingContext.ModelName, propertyName)
            };

            if (await propertyBindingContext.ModelBinder.BindModelAsync(propertyBindingContext))
            {
                var untypedModel = propertyBindingContext.Model;
                var model        = ModelBindingHelper.CastOrDefault <TModel>(untypedModel);
                parentBindingContext.ValidationNode.ChildNodes.Add(propertyBindingContext.ValidationNode);
                return(new BindResult <TModel>(success: true, model: model));
            }

            return(new BindResult <TModel>(success: false, model: default(TModel)));
        }
Exemplo n.º 11
0
        public async Task <ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext.ModelType != typeof(ComplexModelDto))
            {
                return(null);
            }

            ModelBindingHelper.ValidateBindingContext(bindingContext, typeof(ComplexModelDto), allowNullModel: false);

            var dto = (ComplexModelDto)bindingContext.Model;

            foreach (var propertyMetadata in dto.PropertyMetadata)
            {
                var propertyModelName = ModelBindingHelper.CreatePropertyModelName(
                    bindingContext.ModelName,
                    propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName);

                var propertyBindingContext = ModelBindingContext.GetChildModelBindingContext(
                    bindingContext,
                    propertyModelName,
                    propertyMetadata);

                var modelBindingResult =
                    await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(propertyBindingContext);

                if (modelBindingResult == null)
                {
                    // Could not bind. Add a result so MutableObjectModelBinder will check for [DefaultValue].
                    dto.Results[propertyMetadata] =
                        new ModelBindingResult(model: null, key: propertyModelName, isModelSet: false);
                }
                else
                {
                    dto.Results[propertyMetadata] = modelBindingResult;
                }
            }

            return(new ModelBindingResult(dto, bindingContext.ModelName, isModelSet: true));
        }
Exemplo n.º 12
0
 public string AppendTo(string prefix)
 {
     return(ModelBindingHelper.CreatePropertyModelName(prefix, PropertyName));
 }
Exemplo n.º 13
0
        private async Task <bool> CanValueBindAnyModelProperties(MutableObjectBinderContext context)
        {
            // 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 ALL 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 isAnyPropertyEnabledForValueProviderBasedBinding = false;

            foreach (var propertyMetadata in context.PropertyMetadata)
            {
                // 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 propertyModelName = ModelBindingHelper.CreatePropertyModelName(
                        context.ModelBindingContext.ModelName,
                        propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName);

                    var propertyModelBindingContext = ModelBindingContext.GetChildModelBindingContext(
                        context.ModelBindingContext,
                        propertyModelName,
                        propertyMetadata);

                    // If any property can return a true value.
                    if (await CanBindValue(propertyModelBindingContext))
                    {
                        return(true);
                    }
                }
            }

            if (!isAnyPropertyEnabledForValueProviderBasedBinding)
            {
                // Either there are no properties or all the properties are marked as
                // a non value provider based marker.
                // This would be the case when the model has all its properties annotated with
                // a IBinderMetadata. We want to be able to create such a model.
                return(true);
            }

            return(false);
        }