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)); } } }
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); } }