예제 #1
0
        // Internal for testing.
        internal ModelValidationNode ProcessDto(
            ModelBindingContext bindingContext,
            ComplexModelDto dto,
            ModelValidationNode validationNode)
        {
            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 = ModelNames.CreatePropertyModelName(
                    bindingContext.ModelName,
                    propertyName);

                // Get the first 'required' validator (if any) to get custom error message.
                var validatorProviderContext = new ModelValidatorProviderContext(propertyExplorer.Metadata);
                bindingContext.OperationBindingContext.ValidatorProvider.GetValidators(validatorProviderContext);
                var validator = validatorProviderContext.Validators.FirstOrDefault(v => v.IsRequired);

                if (validator != null)
                {
                    addedError = RunValidator(validator, bindingContext, propertyExplorer, modelStateKey);
                }

                // Fall back to default message if BindingBehaviorAttribute required this property and we have no
                // actual validator for it.
                if (!addedError)
                {
                    bindingContext.ModelState.TryAddModelError(
                        modelStateKey,
                        Resources.FormatModelBinding_MissingRequiredMember(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;
                    SetProperty(bindingContext, modelExplorer, propertyMetadata, dtoResult);

                    var dtoValidationNode = dtoResult.ValidationNode;
                    if (dtoValidationNode == null)
                    {
                        // Make sure that irrespective of if the properties of the model were bound with a value,
                        // create a validation node so that these get validated.
                        dtoValidationNode = new ModelValidationNode(dtoResult.Key, entry.Key, dtoResult.Model);
                    }

                    validationNode.ChildNodes.Add(dtoValidationNode);
                }
            }

            return(validationNode);
        }
예제 #2
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 = ModelNames.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);
        }