Exemplo n.º 1
0
        internal async Task <ModelBindingResult> TryBindStrongModel <TModel>(
            ModelBindingContext parentBindingContext,
            string propertyName)
        {
            var propertyModelMetadata =
                parentBindingContext.OperationBindingContext.MetadataProvider.GetMetadataForType(typeof(TModel));
            var propertyModelName =
                ModelNames.CreatePropertyModelName(parentBindingContext.ModelName, propertyName);
            var propertyBindingContext = ModelBindingContext.CreateChildBindingContext(
                parentBindingContext,
                propertyModelMetadata,
                propertyName,
                propertyModelName,
                model: null);

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

            if (result.IsModelSet)
            {
                return(result);
            }
            else
            {
                return(ModelBindingResult.Failed(propertyModelName));
            }
        }
Exemplo n.º 2
0
        internal async Task <ModelBindingResult> TryBindStrongModel <TModel>(
            ModelBindingContext parentBindingContext,
            string propertyName,
            List <ModelValidationNode> childNodes)
        {
            var propertyModelMetadata =
                parentBindingContext.OperationBindingContext.MetadataProvider.GetMetadataForType(typeof(TModel));
            var propertyModelName =
                ModelNames.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)
            {
                if (modelBindingResult.ValidationNode != null)
                {
                    childNodes.Add(modelBindingResult.ValidationNode);
                }

                return(modelBindingResult);
            }

            // Always return a ModelBindingResult to avoid an NRE in BindModelAsync.
            return(new ModelBindingResult(model: default(TModel), key: propertyModelName, isModelSet: false));
        }
Exemplo n.º 3
0
        // Used when the ValueProvider contains the collection to be bound as multiple elements, e.g. foo[0], foo[1].
        private Task <CollectionResult> BindComplexCollection(ModelBindingContext bindingContext)
        {
            var indexPropertyName        = ModelNames.CreatePropertyModelName(bindingContext.ModelName, "index");
            var valueProviderResultIndex = bindingContext.ValueProvider.GetValue(indexPropertyName);
            var indexNames = GetIndexNamesFromValueProviderResult(valueProviderResultIndex);

            return(BindComplexCollectionFromIndexes(bindingContext, indexNames));
        }
Exemplo n.º 4
0
        private static Expression <Func <ModelBindingContext, string, bool> > GetPredicateExpression <TModel>
            (string prefix, Expression <Func <TModel, object> > expression)
        {
            var propertyName = GetPropertyName(expression.Body);
            var property     = ModelNames.CreatePropertyModelName(prefix, propertyName);

            return
                ((context, modelPropertyName) =>
                 property.Equals(ModelNames.CreatePropertyModelName(context.ModelName, modelPropertyName),
                                 StringComparison.OrdinalIgnoreCase));
        }
Exemplo n.º 5
0
        // Internal for testing.
        internal ModelValidationNode ProcessResults(
            ModelBindingContext bindingContext,
            IDictionary <ModelMetadata, ModelBindingResult> results,
            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 = results.Where(p => p.Value.IsModelSet).Select(p => p.Key.PropertyName);

            validationInfo.RequiredProperties.ExceptWith(boundProperties);

            foreach (var missingRequiredProperty in validationInfo.RequiredProperties)
            {
                var propertyExplorer = modelExplorer.GetExplorerForProperty(missingRequiredProperty);
                var propertyName     = propertyExplorer.Metadata.BinderModelName ?? missingRequiredProperty;
                var modelStateKey    = ModelNames.CreatePropertyModelName(bindingContext.ModelName, propertyName);

                bindingContext.ModelState.TryAddModelError(
                    modelStateKey,
                    Resources.FormatModelBinding_MissingBindRequiredMember(propertyName));
            }

            // For each property that BindPropertiesAsync() attempted to bind, call the setter, recording
            // exceptions as necessary.
            foreach (var entry in results)
            {
                var result = entry.Value;
                if (result != null)
                {
                    var propertyMetadata = entry.Key;
                    SetProperty(bindingContext, modelExplorer, propertyMetadata, result);

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

                    validationNode.ChildNodes.Add(propertyValidationNode);
                }
            }

            return(validationNode);
        }
Exemplo n.º 6
0
        // Returned dictionary contains entries corresponding to properties against which binding was attempted. If
        // binding failed, the entry's value will have IsModelSet == false. Binding is attempted for all elements of
        // propertyMetadatas.
        private async Task <IDictionary <ModelMetadata, ModelBindingResult> > BindPropertiesAsync(
            ModelBindingContext bindingContext,
            IEnumerable <ModelMetadata> propertyMetadatas)
        {
            var results = new Dictionary <ModelMetadata, ModelBindingResult>();

            foreach (var propertyMetadata in propertyMetadatas)
            {
                // ModelBindingContext.Model property values may be non-null when invoked via TryUpdateModel(). 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 model = null;
                if (propertyMetadata.PropertyGetter != null &&
                    propertyMetadata.IsComplexType &&
                    !propertyMetadata.ModelType.IsArray)
                {
                    model = propertyMetadata.PropertyGetter(bindingContext.Model);
                }

                var fieldName = propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName;
                var modelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, fieldName);

                var propertyContext = ModelBindingContext.CreateChildBindingContext(
                    bindingContext,
                    propertyMetadata,
                    fieldName: fieldName,
                    modelName: modelName,
                    model: model);

                var result = await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(propertyContext);

                if (result == ModelBindingResult.NoResult)
                {
                    // Could not bind. Let ProcessResult() know explicitly.
                    result = ModelBindingResult.Failed(propertyContext.ModelName);
                }

                results[propertyMetadata] = result;
            }

            return(results);
        }
Exemplo n.º 7
0
        // Returned dictionary contains entries corresponding to properties against which binding was attempted. If
        // binding failed, the entry's value will have IsModelSet == false. Binding is attempted for all elements of
        // propertyMetadatas.
        private async Task <IDictionary <ModelMetadata, ModelBindingResult> > BindPropertiesAsync(
            ModelBindingContext bindingContext,
            IEnumerable <ModelMetadata> propertyMetadatas)
        {
            var results = new Dictionary <ModelMetadata, ModelBindingResult>();

            foreach (var propertyMetadata in propertyMetadatas)
            {
                var propertyModelName = ModelNames.CreatePropertyModelName(
                    bindingContext.ModelName,
                    propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName);
                var childContext = ModelBindingContext.GetChildModelBindingContext(
                    bindingContext,
                    propertyModelName,
                    propertyMetadata);

                // ModelBindingContext.Model property values may be non-null when invoked via TryUpdateModel(). 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.
                //
                // ModelMetadata.PropertyGetter is not null safe; use it only if Model is non-null.
                if (bindingContext.Model != null &&
                    propertyMetadata.PropertyGetter != null &&
                    propertyMetadata.IsComplexType &&
                    !propertyMetadata.ModelType.IsArray)
                {
                    childContext.Model = propertyMetadata.PropertyGetter(bindingContext.Model);
                }

                var result = await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(childContext);

                if (result == null)
                {
                    // Could not bind. Let ProcessResult() know explicitly.
                    result = new ModelBindingResult(model: null, key: propertyModelName, isModelSet: false);
                }

                results[propertyMetadata] = result;
            }

            return(results);
        }
Exemplo n.º 8
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 = ModelNames.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.º 9
0
        // Internal for testing.
        internal void ProcessResults(
            ModelBindingContext bindingContext,
            IDictionary <ModelMetadata, ModelBindingResult> results)
        {
            var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider;
            var metadata         = metadataProvider.GetMetadataForType(bindingContext.ModelType);

            var validationInfo = GetPropertyValidationInfo(bindingContext);

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

            validationInfo.RequiredProperties.ExceptWith(boundProperties);

            foreach (var missingRequiredProperty in validationInfo.RequiredProperties)
            {
                var propertyMetadata = metadata.Properties[missingRequiredProperty];
                var propertyName     = propertyMetadata.BinderModelName ?? missingRequiredProperty;
                var modelStateKey    = ModelNames.CreatePropertyModelName(bindingContext.ModelName, propertyName);

                bindingContext.ModelState.TryAddModelError(
                    modelStateKey,
                    bindingContext.ModelMetadata.ModelBindingMessageProvider.MissingBindRequiredValueAccessor(
                        propertyName));
            }

            // For each property that BindPropertiesAsync() attempted to bind, call the setter, recording
            // exceptions as necessary.
            foreach (var entry in results)
            {
                if (entry.Value != ModelBindingResult.NoResult)
                {
                    var result           = entry.Value;
                    var propertyMetadata = entry.Key;
                    SetProperty(bindingContext, metadata, propertyMetadata, result);
                }
            }
        }
Exemplo n.º 10
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);
        }
Exemplo n.º 11
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);
        }
Exemplo n.º 12
0
        // Internal for testing.
        internal async Task <CollectionResult> BindComplexCollectionFromIndexes(
            ModelBindingContext bindingContext,
            IEnumerable <string> indexNames)
        {
            bool indexNamesIsFinite;

            if (indexNames != null)
            {
                indexNamesIsFinite = true;
            }
            else
            {
                indexNamesIsFinite = false;
                indexNames         = Enumerable.Range(0, int.MaxValue)
                                     .Select(i => i.ToString(CultureInfo.InvariantCulture));
            }

            var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider;
            var elementMetadata  = metadataProvider.GetMetadataForType(typeof(TElement));

            var boundCollection = new List <TElement>();
            var validationNode  = new ModelValidationNode(
                bindingContext.ModelName,
                bindingContext.ModelMetadata,
                boundCollection);

            foreach (var indexName in indexNames)
            {
                var fullChildName       = ModelNames.CreateIndexModelName(bindingContext.ModelName, indexName);
                var childBindingContext = ModelBindingContext.GetChildModelBindingContext(
                    bindingContext,
                    fullChildName,
                    elementMetadata);

                var    didBind    = false;
                object boundValue = null;

                var result =
                    await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(childBindingContext);

                if (result != null && result.IsModelSet)
                {
                    didBind    = true;
                    boundValue = result.Model;
                    if (result.ValidationNode != null)
                    {
                        validationNode.ChildNodes.Add(result.ValidationNode);
                    }
                }

                // infinite size collection stops on first bind failure
                if (!didBind && !indexNamesIsFinite)
                {
                    break;
                }

                boundCollection.Add(ModelBindingHelper.CastOrDefault <TElement>(boundValue));
            }

            return(new CollectionResult
            {
                ValidationNode = validationNode,
                Model = boundCollection
            });
        }
Exemplo n.º 13
0
        // Internal for testing.
        internal async Task <CollectionResult> BindComplexCollectionFromIndexes(
            ModelBindingContext bindingContext,
            IEnumerable <string> indexNames)
        {
            bool indexNamesIsFinite;

            if (indexNames != null)
            {
                indexNamesIsFinite = true;
            }
            else
            {
                indexNamesIsFinite = false;
                indexNames         = Enumerable.Range(0, int.MaxValue)
                                     .Select(i => i.ToString(CultureInfo.InvariantCulture));
            }

            var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider;
            var elementMetadata  = metadataProvider.GetMetadataForType(typeof(TElement));

            var boundCollection = new List <TElement>();

            foreach (var indexName in indexNames)
            {
                var fullChildName       = ModelNames.CreateIndexModelName(bindingContext.ModelName, indexName);
                var childBindingContext = ModelBindingContext.CreateChildBindingContext(
                    bindingContext,
                    elementMetadata,
                    fieldName: indexName,
                    modelName: fullChildName,
                    model: null);


                var    didBind    = false;
                object boundValue = null;

                var result =
                    await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(childBindingContext);

                if (result != null && result.IsModelSet)
                {
                    didBind    = true;
                    boundValue = result.Model;
                }

                // infinite size collection stops on first bind failure
                if (!didBind && !indexNamesIsFinite)
                {
                    break;
                }

                boundCollection.Add(ModelBindingHelper.CastOrDefault <TElement>(boundValue));
            }

            return(new CollectionResult
            {
                Model = boundCollection,

                // If we're working with a fixed set of indexes then this is the format like:
                //
                //  ?parameter.index=zero,one,two&parameter[zero]=0&&parameter[one]=1&parameter[two]=2...
                //
                // We need to provide this data to the validation system so it can 'replay' the keys.
                // But we can't just set ValidationState here, because it needs the 'real' model.
                ValidationStrategy = indexNamesIsFinite ?
                                     new ExplicitIndexCollectionValidationStrategy(indexNames) :
                                     null,
            });
        }