// This isn't extensible right now. // // Returns true if the source is greedy (means to stop exploring the model) // Returns false if the source in unknown or known but not greedy (like [FromQuery]) private static bool GetSource(ModelMetadata metadata, out ApiParameterSource source) { if (metadata.BinderMetadata == null) { // There's nothing we can figure out. source = null; return(false); } if (metadata.BinderMetadata is IFormatterBinderMetadata) { source = ApiParameterSource.Body; return(true); } else if (metadata.BinderMetadata is IHeaderBinderMetadata) { source = ApiParameterSource.Header; return(true); } else if (metadata.BinderMetadata is IServiceActivatorBinderMetadata) { source = ApiParameterSource.Hidden; return(true); } else if (metadata.BinderMetadata is IRouteDataValueProviderMetadata) { source = ApiParameterSource.Path; return(false); } else if (metadata.BinderMetadata is IQueryValueProviderMetadata) { source = ApiParameterSource.Query; return(false); } else if (metadata.BinderMetadata is IFormDataValueProviderMetadata) { source = ApiParameterSource.Form; return(false); } var binderTypeMetadata = metadata.BinderMetadata as IBinderTypeProviderMetadata; if (binderTypeMetadata != null && binderTypeMetadata.BinderType != null) { // This provides it's own model binder, so we can't really make a good // estimate of where it comes from. source = ApiParameterSource.Custom; return(true); } // We're out of cases we know how to handle. source = null; return(false); }
private ApiParameterDescription CreateResult( ModelMetadata metadata, ApiParameterSource source, string containerName) { return(new ApiParameterDescription() { ModelMetadata = metadata, Name = GetName(containerName, metadata), Source = source, Type = metadata.ModelType, }); }
public PropertyKey(ModelMetadata metadata, ApiParameterSource source) { ContainerType = metadata.ContainerType; PropertyName = metadata.PropertyName; Source = source; }
/// <summary> /// Visits a node in a model, and attempts to create <see cref="ApiParameterDescription"/> for any /// model properties where we can definitely compute an answer. /// </summary> /// <param name="modelMetadata">The metadata for the model.</param> /// <param name="ambientSource">The <see cref="ApiParameterSource"/> from the ambient context.</param> /// <param name="containerName">The current name prefix (to prepend to property names).</param> /// <returns> /// <c>true</c> if the set of <see cref="ApiParameterDescription"/> objects were created for the model. /// <c>false</c> if no <see cref="ApiParameterDescription"/> objects were created for the model. /// </returns> /// <remarks> /// Its the reponsibility of this method to create a parameter description for ALL of the current model /// or NONE of it. If a parameter description is created for ANY sub-properties of the model, then a parameter /// description will be created for ALL of them. /// </remarks> private bool Visit(ModelMetadata modelMetadata, ApiParameterSource ambientSource, string containerName) { ApiParameterSource source; if (GetSource(modelMetadata, out source)) { // 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(modelMetadata, source, containerName)); return(true); } // 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.IsCollectionType || !modelMetadata.IsComplexType || !modelMetadata.Properties.Any()) { if (source == null || source == ambientSource) { // If it's a leaf node, and we have no new source then we don't know how to bind this. // Return without creating any parameters, so that this can be included in the parent model. return(false); } else { // We found a new source, and this model has no properties. This is probabaly // a simple type with an attribute like [FromQuery]. Context.Results.Add(CreateResult(modelMetadata, source, containerName)); return(true); } } // This will come from composite model binding - so investigate what's going on with each property. // // Basically once we find something that we know how to bind, we want to treat all properties at that // level (and higher levels) as separate parameters. // // 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 // var propertyCount = 0; var unboundProperties = new HashSet <ModelMetadata>(); // We don't want to append the **parameter** name when building a model name. var newContainerName = containerName; if (modelMetadata.ContainerType != null) { newContainerName = GetName(containerName, modelMetadata); } foreach (var propertyMetadata in modelMetadata.Properties) { propertyCount++; var key = new PropertyKey(propertyMetadata, source); if (Visited.Add(key)) { if (!Visit(propertyMetadata, source ?? ambientSource, newContainerName)) { unboundProperties.Add(propertyMetadata); } } else { unboundProperties.Add(propertyMetadata); } } if (unboundProperties.Count == propertyCount) { if (source == null || source == ambientSource) { // No properties were bound and we didn't find a new source, let the caller handle it. return(false); } else { // We found a new source, and didn't create a result for any of the properties yet, // so create a result for the current object. Context.Results.Add(CreateResult(modelMetadata, source, containerName)); return(true); } } else { // This model was only partially bound, so create a result for all the other properties foreach (var property in unboundProperties) { // Create a 'default' description for each property Context.Results.Add(CreateResult(property, source ?? ambientSource, newContainerName)); } return(true); } }