| File: CascadingParameterState.cs | Web Access |
| Project: src\aspnetcore\src\Components\Components\src\Microsoft.AspNetCore.Components.csproj (Microsoft.AspNetCore.Components) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using Microsoft.AspNetCore.Components.Reflection; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.RenderTree; using static Microsoft.AspNetCore.Internal.LinkerFlags; namespace Microsoft.AspNetCore.Components; internal readonly struct CascadingParameterState (in CascadingParameterInfo parameterInfo, ICascadingValueSupplier valueSupplier, object? key) { private static readonly ConcurrentDictionary<Type, CascadingParameterInfo[]> _cachedInfos = new(); public CascadingParameterInfo ParameterInfo { get; } = parameterInfo; public ICascadingValueSupplier ValueSupplier { get; } = valueSupplier; public object? Key { get; } = key; public CascadingParameterState(in CascadingParameterInfo parameterInfo, ICascadingValueSupplier valueSupplier) : this(parameterInfo, valueSupplier, key: null) { } public static IReadOnlyList<CascadingParameterState> FindCascadingParameters(ComponentState componentState, out bool hasSingleDeliveryParameters) { var componentType = componentState.Component.GetType(); // Suppressed with "pragma warning disable" so ILLink Roslyn Anayzer doesn't report the warning. #pragma warning disable IL2072 // 'componentType' argument does not satisfy 'DynamicallyAccessedMemberTypes.All' in call to 'Microsoft.AspNetCore.Components.CascadingParameterState.GetCascadingParameterInfos(Type)'. var infos = GetCascadingParameterInfos(componentType); #pragma warning restore IL2072 // 'componentType' argument does not satisfy 'DynamicallyAccessedMemberTypes.All' in call to 'Microsoft.AspNetCore.Components.CascadingParameterState.GetCascadingParameterInfos(Type)'. hasSingleDeliveryParameters = false; // For components known not to have any cascading parameters, bail out early if (infos.Length == 0) { return Array.Empty<CascadingParameterState>(); } // Now try to find matches for each of the cascading parameters // Defer instantiation of the result list until we know there's at least one List<CascadingParameterState>? resultStates = null; var numInfos = infos.Length; for (var infoIndex = 0; infoIndex < numInfos; infoIndex++) { ref var info = ref infos[infoIndex]; var supplier = GetMatchingCascadingValueSupplier(info, componentState.Renderer, componentState.LogicalParentComponentState); if (supplier != null) { // Although not all parameters might be matched, we know the maximum number resultStates ??= new List<CascadingParameterState>(infos.Length - infoIndex); resultStates.Add(new CascadingParameterState(info, supplier, componentState)); if (info.Attribute.SingleDelivery) { hasSingleDeliveryParameters = true; if (!supplier.IsFixed) { // We don't have a use case for IsFixed=false with SingleDelivery=true. To avoid complications about // subscribing/unsubscribing in this case, just disallow it. It shouldn't be possible for this to // occur unless someone creates their own CascadingParameterAttributeBase subclass. throw new InvalidOperationException($"'{info.Attribute.GetType()}' is flagged with SingleDelivery, but the selected supplier '{supplier.GetType()}' is not flagged with {nameof(ICascadingValueSupplier.IsFixed)}"); } } } } return resultStates ?? (IReadOnlyList<CascadingParameterState>)Array.Empty<CascadingParameterState>(); } internal static ICascadingValueSupplier? GetMatchingCascadingValueSupplier(in CascadingParameterInfo info, Renderer renderer, ComponentState? componentState) { // First scan up through the component hierarchy var candidate = componentState; while (candidate is not null) { if (candidate.Component is ICascadingValueSupplier valueSupplier && valueSupplier.CanSupplyValue(info)) { return valueSupplier; } candidate = candidate.LogicalParentComponentState; } // We got to the root and found no match, so now look at the providers registered in DI foreach (var valueSupplier in renderer.ServiceProviderCascadingValueSuppliers) { if (valueSupplier.CanSupplyValue(info)) { return valueSupplier; } } // No match return null; } private static CascadingParameterInfo[] GetCascadingParameterInfos( [DynamicallyAccessedMembers(Component)] Type componentType) { if (!_cachedInfos.TryGetValue(componentType, out var infos)) { infos = CreateCascadingParameterInfos(componentType); _cachedInfos[componentType] = infos; } return infos; } private static CascadingParameterInfo[] CreateCascadingParameterInfos( [DynamicallyAccessedMembers(Component)] Type componentType) { List<CascadingParameterInfo>? result = null; var candidateProps = ComponentProperties.GetCandidateBindableProperties(componentType); foreach (var prop in candidateProps) { var cascadingParameterAttribute = prop.GetCustomAttributes() .OfType<CascadingParameterAttributeBase>().SingleOrDefault(); if (cascadingParameterAttribute != null) { result ??= new List<CascadingParameterInfo>(); result.Add(new CascadingParameterInfo( cascadingParameterAttribute, prop.Name, prop.PropertyType)); } } return result?.ToArray() ?? Array.Empty<CascadingParameterInfo>(); } }