File: src\Components\Shared\src\RootTypeCache.cs
Web Access
Project: src\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 Microsoft.AspNetCore.Components.HotReload;
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
 
#if COMPONENTS
namespace Microsoft.AspNetCore.Components.Infrastructure;
#else
namespace Microsoft.AspNetCore.Components;
#endif
 
// A cache for root component types
internal sealed class RootTypeCache : IDisposable
{
    private readonly ConcurrentDictionary<Key, Type?> _typeToKeyLookUp = new();
 
    public RootTypeCache()
    {
        if (HotReloadManager.Default.MetadataUpdateSupported)
        {
            HotReloadManager.Default.OnDeltaApplied += ClearCache;
        }
    }
 
    internal void ClearCache() => _typeToKeyLookUp.Clear();
 
    public void Dispose()
    {
        if (HotReloadManager.Default.MetadataUpdateSupported)
        {
            HotReloadManager.Default.OnDeltaApplied -= ClearCache;
        }
    }
 
    public Type? GetRootType(string assembly, string type)
    {
        var key = new Key(assembly, type);
        if (_typeToKeyLookUp.TryGetValue(key, out var resolvedType))
        {
            return resolvedType;
        }
        else
        {
            return _typeToKeyLookUp.GetOrAdd(key, ResolveType, AppDomain.CurrentDomain.GetAssemblies());
        }
    }
 
    [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Root components are expected to be defined in assemblies that do not get trimmed.")]
    private static Type? ResolveType(Key key, Assembly[] assemblies)
    {
        Assembly? assembly = null;
        for (var i = 0; i < assemblies.Length; i++)
        {
            var current = assemblies[i];
            if (current.GetName().Name == key.Assembly)
            {
                assembly = current;
                break;
            }
        }
 
        if (assembly == null)
        {
            // It might be that the assembly is not loaded yet, this can happen if the root component is defined in a
            // different assembly than the app and there is no reference from the app assembly to any type in the class
            // library that has been used yet.
            // In this case, try and load the assembly and look up the type again.
            // We only need to do this in the browser because its a different process, in the server the assembly will already
            // be loaded.
            if (OperatingSystem.IsBrowser())
            {
                try
                {
                    assembly = Assembly.Load(key.Assembly);
                }
                catch
                {
                    // It's fine to ignore the exception, since we'll return null below.
                }
            }
        }
 
        return assembly?.GetType(key.Type, throwOnError: false, ignoreCase: false);
    }
 
    private readonly struct Key : IEquatable<Key>
    {
        public Key(string assembly, string type) =>
            (Assembly, Type) = (assembly, type);
 
        public string Assembly { get; }
 
        public string Type { get; }
 
        public override bool Equals(object? obj) => obj is Key key && Equals(key);
 
        public bool Equals(Key other) => string.Equals(Assembly, other.Assembly, StringComparison.Ordinal) &&
            string.Equals(Type, other.Type, StringComparison.Ordinal);
 
        public override int GetHashCode() => HashCode.Combine(Assembly, Type);
    }
}