private IList <ApiParameterDescription> GetParameters(ApiParameterContext context) { // First, get parameters from the model-binding/parameter-binding side of the world. if (context.ActionDescriptor.Parameters != null) { foreach (var actionParameter in context.ActionDescriptor.Parameters) { var visitor = new PseudoModelBindingVisitor(context, actionParameter); ModelMetadata metadata = null; if (_mvcOptions.AllowValidatingTopLevelNodes && actionParameter is ControllerParameterDescriptor controllerParameterDescriptor && _modelMetadataProvider is ModelMetadataProvider provider) { // The default model metadata provider derives from ModelMetadataProvider // and can therefore supply information about attributes applied to parameters. metadata = provider.GetMetadataForParameter(controllerParameterDescriptor.ParameterInfo); } else { // For backward compatibility, if there's a custom model metadata provider that // only implements the older IModelMetadataProvider interface, access the more // limited metadata information it supplies. In this scenario, validation attributes // are not supported on parameters. metadata = _modelMetadataProvider.GetMetadataForType(actionParameter.ParameterType); } var bindingContext = ApiParameterDescriptionContext.GetContext( metadata, actionParameter.BindingInfo, propertyName: actionParameter.Name); visitor.WalkParameter(bindingContext); }
ApiParameterDescription CreateResult(ApiParameterDescriptionContext bindingContext, BindingSource source, string containerName) { var modelMetadata = bindingContext.ModelMetadata; var type = modelMetadata.ModelType; if (type.IsODataActionParameters() && Context.RouteContext.Operation?.IsAction() == true) { var action = (IEdmAction)Context.RouteContext.Operation; var apiVersion = Context.RouteContext.ApiVersion; type = Context.TypeBuilder.NewActionParameters(Context.Services, action, apiVersion, Context.RouteContext.ActionDescriptor.ControllerName); } else { type = type.SubstituteIfNecessary(new TypeSubstitutionContext(Context.Services, Context.TypeBuilder)); } return(new ApiParameterDescription() { ModelMetadata = modelMetadata.SubstituteIfNecessary(type), Name = GetName(containerName, bindingContext), Source = source, Type = type, ParameterDescriptor = Parameter, }); }
public void WalkParameter(ApiParameterDescriptionContext context) { // Attempt to find a binding source for the parameter // // The default is ModelBinding (aka all default value providers) var source = BindingSource.ModelBinding; Visit(context, source, containerName: string.Empty); }
private static string GetName(string containerName, ApiParameterDescriptionContext metadata) { if (string.IsNullOrEmpty(metadata.BinderModelName)) { return(ModelNames.CreatePropertyModelName(containerName, metadata.PropertyName)); } return(metadata.BinderModelName); }
private static string GetName(string containerName, ApiParameterDescriptionContext metadata) { if (!string.IsNullOrEmpty(metadata.BinderModelName)) { // Name was explicitly provided return(metadata.BinderModelName); } else { return(ModelNames.CreatePropertyModelName(containerName, metadata.PropertyName)); } }
private ApiParameterDescription CreateResult( ApiParameterDescriptionContext bindingContext, BindingSource source, string containerName) { return(new ApiParameterDescription() { ModelMetadata = bindingContext.ModelMetadata, Name = GetName(containerName, bindingContext), Source = source, Type = bindingContext.ModelMetadata.ModelType, }); }
void Visit(ApiParameterDescriptionContext bindingContext, BindingSource ambientSource, string containerName) { var source = bindingContext.BindingSource; if (source != null && source.IsGreedy) { Context.Results.Add(CreateResult(bindingContext, source, containerName)); return; } var modelMetadata = bindingContext.ModelMetadata; if (modelMetadata.IsEnumerableType || !modelMetadata.IsComplexType || modelMetadata.Properties.Count == 0) { Context.Results.Add(CreateResult(bindingContext, source ?? ambientSource, containerName)); return; } var newContainerName = containerName; if (modelMetadata.ContainerType != null) { newContainerName = GetName(containerName, bindingContext); } source ??= ambientSource; for (var i = 0; i < modelMetadata.Properties.Count; i++) { var propertyMetadata = modelMetadata.Properties[i]; var key = new PropertyKey(propertyMetadata, source); var propertyContext = new ApiParameterDescriptionContext(propertyMetadata, bindingInfo: null, propertyName: null); if (Visited.Add(key)) { Visit(propertyContext, source, newContainerName); } else { Context.Results.Add(CreateResult(propertyContext, source, newContainerName)); } } }
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 wierd 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); } for (var i = 0; i < modelMetadata.Properties.Count; i++) { var propertyMetadata = modelMetadata.Properties[i]; var key = new PropertyKey(propertyMetadata, source); var propertyContext = ApiParameterDescriptionContext.GetContext( propertyMetadata, bindingInfo: null, propertyName: null); if (Visited.Add(key)) { Visit(propertyContext, source ?? ambientSource, newContainerName); } else { // This is cycle, so just add a result rather than traversing. Context.Results.Add(CreateResult(propertyContext, source ?? ambientSource, newContainerName)); } } }
private IList <ApiParameterDescription> GetParameters(ApiParameterContext context) { // First, get parameters from the model-binding/parameter-binding side of the world. if (context.ActionDescriptor.Parameters != null) { foreach (var actionParameter in context.ActionDescriptor.Parameters) { var visitor = new PseudoModelBindingVisitor(context, actionParameter); var metadata = _modelMetadataProvider.GetMetadataForType(actionParameter.ParameterType); var bindingContext = ApiParameterDescriptionContext.GetContext( metadata, actionParameter.BindingInfo, propertyName: actionParameter.Name); visitor.WalkParameter(bindingContext); } } if (context.ActionDescriptor.BoundProperties != null) { foreach (var actionParameter in context.ActionDescriptor.BoundProperties) { var visitor = new PseudoModelBindingVisitor(context, actionParameter); var modelMetadata = context.MetadataProvider.GetMetadataForProperty( containerType: context.ActionDescriptor.ControllerTypeInfo.AsType(), propertyName: actionParameter.Name); var bindingContext = ApiParameterDescriptionContext.GetContext( modelMetadata, actionParameter.BindingInfo, propertyName: actionParameter.Name); visitor.WalkParameter(bindingContext); } } for (var i = context.Results.Count - 1; i >= 0; i--) { // Remove any 'hidden' parameters. These are things that can't come from user input, // so they aren't worth showing. if (!context.Results[i].Source.IsFromRequest) { context.Results.RemoveAt(i); } } // Next, we want to join up any route parameters with those discovered from the action's parameters. var routeParameters = new Dictionary <string, ApiParameterRouteInfo>(StringComparer.OrdinalIgnoreCase); foreach (var routeParameter in context.RouteParameters) { routeParameters.Add(routeParameter.Name, CreateRouteInfo(routeParameter)); } foreach (var parameter in context.Results) { if (parameter.Source == BindingSource.Path || parameter.Source == BindingSource.ModelBinding || parameter.Source == BindingSource.Custom) { ApiParameterRouteInfo routeInfo; if (routeParameters.TryGetValue(parameter.Name, out routeInfo)) { parameter.RouteInfo = routeInfo; routeParameters.Remove(parameter.Name); if (parameter.Source == BindingSource.ModelBinding && !parameter.RouteInfo.IsOptional) { // If we didn't see any information about the parameter, but we have // a route parameter that matches, let's switch it to path. parameter.Source = BindingSource.Path; } } } } // Lastly, create a parameter representation for each route parameter that did not find // a partner. foreach (var routeParameter in routeParameters) { context.Results.Add(new ApiParameterDescription() { Name = routeParameter.Key, RouteInfo = routeParameter.Value, Source = BindingSource.Path, }); } return(context.Results); }
internal void WalkParameter(ApiParameterDescriptionContext context) => Visit(context, BindingSource.ModelBinding, containerName: string.Empty);