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