| File: ModelBinding\Metadata\DefaultBindingMetadataProvider.cs | Web Access |
| Project: src\aspnetcore\src\Mvc\Mvc.Core\src\Microsoft.AspNetCore.Mvc.Core.csproj (Microsoft.AspNetCore.Mvc.Core) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #nullable enable using System.Linq; using System.Reflection; using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; /// <summary> /// A default implementation of <see cref="IBindingMetadataProvider"/>. /// </summary> internal sealed class DefaultBindingMetadataProvider : IBindingMetadataProvider { public void CreateBindingMetadata(BindingMetadataProviderContext context) { ArgumentNullException.ThrowIfNull(context); // BinderModelName foreach (var binderModelNameAttribute in context.Attributes.OfType<IModelNameProvider>()) { if (binderModelNameAttribute.Name != null) { context.BindingMetadata.BinderModelName = binderModelNameAttribute.Name; break; } } // BinderType foreach (var binderTypeAttribute in context.Attributes.OfType<IBinderTypeProviderMetadata>()) { if (binderTypeAttribute.BinderType != null) { context.BindingMetadata.BinderType = binderTypeAttribute.BinderType; break; } } // BindingSource foreach (var bindingSourceAttribute in context.Attributes.OfType<IBindingSourceMetadata>()) { if (bindingSourceAttribute.BindingSource != null) { context.BindingMetadata.BindingSource = bindingSourceAttribute.BindingSource; break; } } // PropertyFilterProvider var propertyFilterProviders = context.Attributes.OfType<IPropertyFilterProvider>().ToArray(); if (propertyFilterProviders.Length == 0) { context.BindingMetadata.PropertyFilterProvider = null; } else if (propertyFilterProviders.Length == 1) { context.BindingMetadata.PropertyFilterProvider = propertyFilterProviders[0]; } else { var composite = new CompositePropertyFilterProvider(propertyFilterProviders); context.BindingMetadata.PropertyFilterProvider = composite; } var bindingBehavior = FindBindingBehavior(context); if (bindingBehavior != null) { context.BindingMetadata.IsBindingAllowed = bindingBehavior.Behavior != BindingBehavior.Never; context.BindingMetadata.IsBindingRequired = bindingBehavior.Behavior == BindingBehavior.Required; } if (GetBoundConstructor(context.Key.ModelType) is ConstructorInfo constructorInfo) { context.BindingMetadata.BoundConstructor = constructorInfo; } } internal static ConstructorInfo? GetBoundConstructor(Type type) { if (type.IsAbstract || type.IsValueType || type.IsInterface) { return null; } var constructors = type.GetConstructors(); if (constructors.Length == 0) { return null; } return GetRecordTypeConstructor(type, constructors); } private static ConstructorInfo? GetRecordTypeConstructor(Type type, ConstructorInfo[] constructors) { if (!IsRecordType(type)) { return null; } // For record types, we will support binding and validating the primary constructor. // There isn't metadata to identify a primary constructor. Our heuristic is: // We require exactly one constructor to be defined on the type, and that every parameter on // that constructor is mapped to a property with the same name and type. if (constructors.Length > 1) { return null; } var constructor = constructors[0]; var parameters = constructor.GetParameters(); if (parameters.Length == 0) { // We do not need to do special handling for parameterless constructors. return null; } var properties = PropertyHelper.GetVisibleProperties(type); for (var i = 0; i < parameters.Length; i++) { var parameter = parameters[i]; var mappedProperty = properties.FirstOrDefault(property => string.Equals(property.Name, parameter.Name, StringComparison.Ordinal) && property.Property.PropertyType == parameter.ParameterType); if (mappedProperty is null) { // No property found, this is not a primary constructor. return null; } } return constructor; static bool IsRecordType(Type type) { // Based on the state of the art as described in https://github.com/dotnet/roslyn/issues/45777 var cloneMethod = type.GetMethod("<Clone>$", BindingFlags.Public | BindingFlags.Instance); return cloneMethod != null && (cloneMethod.ReturnType == type || cloneMethod.ReturnType == type.BaseType); } } private static BindingBehaviorAttribute? FindBindingBehavior(BindingMetadataProviderContext context) { switch (context.Key.MetadataKind) { case ModelMetadataKind.Property: // BindingBehavior can fall back to attributes on the Container Type, but we should ignore // attributes on the Property Type. var matchingAttributes = context.PropertyAttributes!.OfType<BindingBehaviorAttribute>(); return matchingAttributes.FirstOrDefault() ?? context.Key.ContainerType! .GetCustomAttributes(typeof(BindingBehaviorAttribute), inherit: true) .OfType<BindingBehaviorAttribute>() .FirstOrDefault(); case ModelMetadataKind.Parameter: return context.ParameterAttributes!.OfType<BindingBehaviorAttribute>().FirstOrDefault(); default: return null; } } private sealed class CompositePropertyFilterProvider : IPropertyFilterProvider { private readonly IEnumerable<IPropertyFilterProvider> _providers; public CompositePropertyFilterProvider(IEnumerable<IPropertyFilterProvider> providers) { _providers = providers; } public Func<ModelMetadata, bool> PropertyFilter => CreatePropertyFilter(); private Func<ModelMetadata, bool> CreatePropertyFilter() { var propertyFilters = _providers .Select(p => p.PropertyFilter) .Where(p => p != null); return (m) => { foreach (var propertyFilter in propertyFilters) { if (!propertyFilter(m)) { return false; } } return true; }; } } }