| File: src\aspnetcore\src\Shared\PropertyAsParameterInfo.cs | Web Access |
| Project: src\aspnetcore\src\Mvc\Mvc.ApiExplorer\src\Microsoft.AspNetCore.Mvc.ApiExplorer.csproj (Microsoft.AspNetCore.Mvc.ApiExplorer) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Http; internal sealed class PropertyAsParameterInfo : ParameterInfo { private readonly PropertyInfo _underlyingProperty; private readonly ParameterInfo? _constructionParameterInfo; private readonly NullabilityInfoContext _nullabilityContext; private NullabilityInfo? _nullabilityInfo; public PropertyAsParameterInfo(PropertyInfo propertyInfo, NullabilityInfoContext? nullabilityContext = null) { Debug.Assert(propertyInfo != null, "PropertyInfo must be provided."); AttrsImpl = (ParameterAttributes)propertyInfo.Attributes; NameImpl = propertyInfo.Name; MemberImpl = propertyInfo; ClassImpl = propertyInfo.PropertyType; // It is not a real parameter in the delegate, so, // not defining a real position. PositionImpl = -1; _nullabilityContext = nullabilityContext ?? new NullabilityInfoContext(); _underlyingProperty = propertyInfo; } public PropertyAsParameterInfo(PropertyInfo property, ParameterInfo parameterInfo, NullabilityInfoContext? nullabilityContext = null) : this(property, nullabilityContext) { _constructionParameterInfo = parameterInfo; } public override bool HasDefaultValue => _constructionParameterInfo is not null && _constructionParameterInfo.HasDefaultValue; public override object? DefaultValue => _constructionParameterInfo?.DefaultValue; public override int MetadataToken => _underlyingProperty.MetadataToken; public override object? RawDefaultValue => _constructionParameterInfo?.RawDefaultValue; /// <summary> /// Unwraps all parameters that contains <see cref="AsParametersAttribute"/> and /// creates a flat list merging the current parameters, not including the /// parameters that contain a <see cref="AsParametersAttribute"/>, and all additional /// parameters detected. /// </summary> /// <param name="parameters">List of parameters to be flattened.</param> /// <param name="cache">An instance of the method cache class.</param> /// <returns>Flat list of parameters.</returns> [RequiresUnreferencedCode("Uses unbounded Reflection to access parameter type constructors.")] public static ReadOnlySpan<ParameterInfo> Flatten(ParameterInfo[] parameters, ParameterBindingMethodCache cache) { ArgumentNullException.ThrowIfNull(parameters); ArgumentNullException.ThrowIfNull(cache); if (parameters.Length == 0) { return Array.Empty<ParameterInfo>(); } List<ParameterInfo>? flattenedParameters = null; NullabilityInfoContext? nullabilityContext = null; for (var i = 0; i < parameters.Length; i++) { if (parameters[i].Name is null) { throw new InvalidOperationException($"Encountered a parameter of type '{parameters[i].ParameterType}' without a name. Parameters must have a name."); } if (parameters[i].CustomAttributes.Any(a => a.AttributeType == typeof(AsParametersAttribute))) { // Initialize the list with all parameter already processed // to keep the same parameter ordering static List<ParameterInfo> InitializeList(ParameterInfo[] parameters, int i) { // will add the rest of the parameters to this list, so set initial capacity to reduce growing the list List<ParameterInfo> list = new(parameters.Length); list.AddRange(parameters.AsSpan(0, i)); return list; } flattenedParameters ??= InitializeList(parameters, i); nullabilityContext ??= new(); var isNullable = Nullable.GetUnderlyingType(parameters[i].ParameterType) != null || nullabilityContext.Create(parameters[i])?.ReadState == NullabilityState.Nullable; if (isNullable) { throw new InvalidOperationException($"The nullable type '{TypeNameHelper.GetTypeDisplayName(parameters[i].ParameterType, fullName: false)}' is not supported."); } var (constructor, constructorParameters) = cache.FindConstructor(parameters[i].ParameterType); if (constructor is not null && constructorParameters is { Length: > 0 }) { foreach (var constructorParameter in constructorParameters) { flattenedParameters.Add( new PropertyAsParameterInfo( constructorParameter.PropertyInfo, constructorParameter.ParameterInfo, nullabilityContext)); } } else { var properties = parameters[i].ParameterType.GetProperties(); foreach (var property in properties) { if (property.CanWrite) { flattenedParameters.Add(new PropertyAsParameterInfo(property, nullabilityContext)); } } } } else { flattenedParameters?.Add(parameters[i]); } } return flattenedParameters is not null ? CollectionsMarshal.AsSpan(flattenedParameters) : parameters.AsSpan(); } public override object[] GetCustomAttributes(Type attributeType, bool inherit) { var constructorAttributes = _constructionParameterInfo?.GetCustomAttributes(attributeType, inherit); var propertyAttributes = _underlyingProperty.GetCustomAttributes(attributeType, inherit); // We don't have constructor attributes, so we can safely return the property attributes. if (constructorAttributes == null || constructorAttributes.Length == 0) { return propertyAttributes; } // We don't have property attributes, so we can safely return the constructor attributes. if (propertyAttributes.Length == 0) { return constructorAttributes; } // We have both, and we need to merge (rare scenario). var arrayType = constructorAttributes.GetType(); var mergedAttributes = Array.CreateInstanceFromArrayType(arrayType, constructorAttributes.Length + propertyAttributes.Length); Array.Copy(constructorAttributes, mergedAttributes, constructorAttributes.Length); Array.Copy(propertyAttributes, 0, mergedAttributes, constructorAttributes.Length, propertyAttributes.Length); return (object[])mergedAttributes; } public override object[] GetCustomAttributes(bool inherit) { var constructorAttributes = _constructionParameterInfo?.GetCustomAttributes(inherit); var propertyAttributes = _underlyingProperty.GetCustomAttributes(inherit); // We don't have constructor attributes, so we can safely return the property attributes. if (constructorAttributes == null || constructorAttributes.Length == 0) { return propertyAttributes; } // We don't have property attributes, so we can safely return the constructor attributes. if (propertyAttributes.Length == 0) { return constructorAttributes; } // We have both, and we need to merge (rare scenario). var arrayType = constructorAttributes.GetType(); var mergedAttributes = Array.CreateInstanceFromArrayType(arrayType, constructorAttributes.Length + propertyAttributes.Length); Array.Copy(constructorAttributes, mergedAttributes, constructorAttributes.Length); Array.Copy(propertyAttributes, 0, mergedAttributes, constructorAttributes.Length, propertyAttributes.Length); return (object[])mergedAttributes; } public override IList<CustomAttributeData> GetCustomAttributesData() { var attributes = new List<CustomAttributeData>( _constructionParameterInfo?.GetCustomAttributesData() ?? Array.Empty<CustomAttributeData>()); attributes.AddRange(_underlyingProperty.GetCustomAttributesData()); return attributes.AsReadOnly(); } public override Type[] GetOptionalCustomModifiers() => _underlyingProperty.GetOptionalCustomModifiers(); public override Type[] GetRequiredCustomModifiers() => _underlyingProperty.GetRequiredCustomModifiers(); public override bool IsDefined(Type attributeType, bool inherit) { return (_constructionParameterInfo is not null && _constructionParameterInfo.IsDefined(attributeType, inherit)) || _underlyingProperty.IsDefined(attributeType, inherit); } public new bool IsOptional => NullabilityInfo.ReadState switch { // Anything nullable is optional NullabilityState.Nullable => true, // In an oblivious context, the required modifier makes // members non-optional NullabilityState.Unknown => !_underlyingProperty.GetCustomAttributes().OfType<RequiredMemberAttribute>().Any(), // Non-nullable types are only optional if they have a default // value NullabilityState.NotNull => HasDefaultValue, // Assume that types are optional by default so we // don't greedily opt parameters into param checking _ => true }; public NullabilityInfo NullabilityInfo => _nullabilityInfo ??= _constructionParameterInfo is not null ? _nullabilityContext.Create(_constructionParameterInfo) : _nullabilityContext.Create(_underlyingProperty); }