private void Visit(
                ApiParameterDescriptionContext bindingContext,
                BindingSource ambientSource,
                string containerName)
            {
                var source = bindingContext.BindingSource;

                if (source != null && source.IsGreedy)
                {
                    // We have a definite answer for this model. This is a greedy source like
                    // [FromBody] so there's no need to consider properties.
                    Context.Results.Add(CreateResult(bindingContext, source, containerName));
                    return;
                }

                var modelMetadata = bindingContext.ModelMetadata;

                // For any property which is a leaf node, we don't want to keep traversing:
                //
                //  1)  Collections - while it's possible to have binder attributes on the inside of a collection,
                //      it hardly seems useful, and would result in some very weird binding.
                //
                //  2)  Simple Types - These are generally part of the .net framework - primitives, or types which have a
                //      type converter from string.
                //
                //  3)  Types with no properties. Obviously nothing to explore there.
                //
                if (modelMetadata.IsEnumerableType ||
                    !modelMetadata.IsComplexType ||
                    modelMetadata.Properties.Count == 0)
                {
                    Context.Results.Add(CreateResult(bindingContext, source ?? ambientSource, containerName));
                    return;
                }

                // This will come from composite model binding - so investigate what's going on with each property.
                //
                // Ex:
                //
                //      public IActionResult PlaceOrder(OrderDTO order) {...}
                //
                //      public class OrderDTO
                //      {
                //          public int AccountId { get; set; }
                //
                //          [FromBody]
                //          public Order { get; set; }
                //      }
                //
                // This should result in two parameters:
                //
                //  AccountId - source: Any
                //  Order - source: Body
                //

                // We don't want to append the **parameter** name when building a model name.
                var newContainerName = containerName;

                if (modelMetadata.ContainerType != null)
                {
                    newContainerName = GetName(containerName, bindingContext);
                }

                var metadataProperties      = modelMetadata.Properties;
                var metadataPropertiesCount = metadataProperties.Count;

                for (var i = 0; i < metadataPropertiesCount; i++)
                {
                    var propertyMetadata = metadataProperties[i];
                    var key         = new PropertyKey(propertyMetadata, source);
                    var bindingInfo = BindingInfo.GetBindingInfo(Enumerable.Empty <object>(), propertyMetadata);

                    var propertyContext = ApiParameterDescriptionContext.GetContext(
                        propertyMetadata,
                        bindingInfo: bindingInfo,
                        propertyName: null);

                    if (Visited.Add(key))
                    {
                        Visit(propertyContext, source ?? ambientSource, newContainerName);
                        Visited.Remove(key);
                    }
                    else
                    {
                        // This is cycle, so just add a result rather than traversing.
                        Context.Results.Add(CreateResult(propertyContext, source ?? ambientSource, newContainerName));
                    }
                }
            }
示例#2
0
        public async Task BindModelAsync_WithBindPageProperty_EnforcesBindRequired(int?input, bool isValid)
        {
            // Arrange
            var propertyInfo       = typeof(TestPage).GetProperty(nameof(TestPage.BindRequiredProperty));
            var propertyDescriptor = new PageBoundPropertyDescriptor
            {
                BindingInfo = BindingInfo.GetBindingInfo(new[]
                {
                    new FromQueryAttribute {
                        Name = propertyInfo.Name
                    },
                }),
                Name          = propertyInfo.Name,
                ParameterType = propertyInfo.PropertyType,
                Property      = propertyInfo,
            };

            var typeInfo         = typeof(TestPage).GetTypeInfo();
            var actionDescriptor = new CompiledPageActionDescriptor
            {
                BoundProperties = new[] { propertyDescriptor },
                HandlerTypeInfo = typeInfo,
                ModelTypeInfo   = typeInfo,
                PageTypeInfo    = typeInfo,
            };

            var testContext = ModelBindingTestHelper.GetTestContext(request =>
            {
                request.Method = "POST";
                if (input.HasValue)
                {
                    request.QueryString = new QueryString($"?{propertyDescriptor.Name}={input.Value}");
                }
            });

            var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
            var parameterBinder       = ModelBindingTestHelper.GetParameterBinder(modelMetadataProvider);
            var modelBinderFactory    = ModelBindingTestHelper.GetModelBinderFactory(modelMetadataProvider);
            var modelMetadata         = modelMetadataProvider
                                        .GetMetadataForProperty(typeof(TestPage), propertyDescriptor.Name);

            var pageBinder = PageBinderFactory.CreatePropertyBinder(
                parameterBinder,
                modelMetadataProvider,
                modelBinderFactory,
                actionDescriptor);
            var pageContext = new PageContext
            {
                ActionDescriptor       = actionDescriptor,
                HttpContext            = testContext.HttpContext,
                RouteData              = testContext.RouteData,
                ValueProviderFactories = testContext.ValueProviderFactories,
            };

            var page = new TestPage();

            // Act
            await pageBinder(pageContext, page);

            // Assert
            Assert.Equal(isValid, pageContext.ModelState.IsValid);
            if (isValid)
            {
                Assert.Equal(input.Value, page.BindRequiredProperty);
            }
        }