| File: AssemblyLoader.cs | Web Access |
| Project: src\src\Aspire.Hosting.RemoteHost\Aspire.Hosting.RemoteHost.csproj (Aspire.Hosting.RemoteHost) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Reflection; using System.Runtime.Loader; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace Aspire.Hosting.RemoteHost; /// <summary> /// Service that loads assemblies on demand with proper logging. /// </summary> internal sealed class AssemblyLoader { private readonly Lazy<IReadOnlyList<Assembly>> _assemblies; private readonly string _applicationBasePath; private readonly IntegrationLoadContext _loadContext; public AssemblyLoader(IConfiguration configuration, ILogger<AssemblyLoader> logger) { _applicationBasePath = AppContext.BaseDirectory; var libsPath = configuration["ASPIRE_INTEGRATION_LIBS_PATH"]; _loadContext = CreateLoadContext(libsPath, _applicationBasePath, logger); // ASPIRE_INTEGRATION_LIBS_PATH is set by the CLI when running guest (polyglot) apphosts // that require additional hosting integration packages. See docs/specs/bundle.md for details. logger.LogDebug( "Using load context {LoadContext} for integration assemblies. Integration libs path: {Path}", _loadContext.Name ?? "<unknown>", string.IsNullOrWhiteSpace(libsPath) ? "<none>" : libsPath); _assemblies = new Lazy<IReadOnlyList<Assembly>>( () => LoadAssemblies(configuration, _loadContext, logger, libsPath, _applicationBasePath)); } public IReadOnlyList<Assembly> GetAssemblies() => _assemblies.Value; internal static IReadOnlyList<string> GetAssemblyNamesToLoad( IConfiguration configuration, string? integrationLibsPath, string applicationBasePath) { var assemblyNames = new List<string>(); var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (var name in configuration.GetSection("AtsAssemblies").Get<string[]>() ?? []) { if (!string.IsNullOrWhiteSpace(name) && seen.Add(name)) { assemblyNames.Add(name); } } foreach (var name in DiscoverAspireHostingAssemblies([integrationLibsPath, applicationBasePath])) { if (seen.Add(name)) { assemblyNames.Add(name); } } return assemblyNames; } internal static IReadOnlyList<string> DiscoverAspireHostingAssemblies(IEnumerable<string?> directories) { var assemblyNames = new SortedSet<string>(StringComparer.OrdinalIgnoreCase); foreach (var directory in directories) { if (string.IsNullOrWhiteSpace(directory) || !Directory.Exists(directory)) { continue; } foreach (var file in Directory.EnumerateFiles(directory, "Aspire*.dll", SearchOption.TopDirectoryOnly)) { var assemblyName = Path.GetFileNameWithoutExtension(file); if (IsAutoDiscoveredAssembly(assemblyName)) { assemblyNames.Add(assemblyName); } } } return assemblyNames.ToList(); } private static bool IsAutoDiscoveredAssembly(string? assemblyName) { if (string.IsNullOrWhiteSpace(assemblyName)) { return false; } if (assemblyName.Equals("Aspire.Hosting.AppHost", StringComparison.OrdinalIgnoreCase) || assemblyName.StartsWith("Aspire.AppHost.", StringComparison.OrdinalIgnoreCase)) { return false; } return assemblyName.Equals("Aspire.Hosting", StringComparison.OrdinalIgnoreCase) || assemblyName.StartsWith("Aspire.Hosting.", StringComparison.OrdinalIgnoreCase); } private static List<Assembly> LoadAssemblies( IConfiguration configuration, IntegrationLoadContext loadContext, ILogger logger, string? integrationLibsPath, string applicationBasePath) { var assemblyNames = GetAssemblyNamesToLoad(configuration, integrationLibsPath, applicationBasePath); var assemblies = new List<Assembly>(); foreach (var name in assemblyNames) { try { var assembly = LoadAssembly(loadContext, name); assemblies.Add(assembly); logger.LogDebug("Loaded assembly: {AssemblyName} in {LoadContext}", assembly.FullName, AssemblyLoadContext.GetLoadContext(assembly)?.Name ?? "<unknown>"); } catch (Exception ex) { logger.LogWarning(ex, "Failed to load assembly '{AssemblyName}'", name); } } return assemblies; } private static Assembly LoadAssembly(IntegrationLoadContext loadContext, string name) { var assemblyName = new AssemblyName(name); return loadContext.LoadFromAssemblyName(assemblyName); } private static IntegrationLoadContext CreateLoadContext(string? integrationLibsPath, string applicationBasePath, ILogger logger) { var probeDirs = new List<string>(); if (!string.IsNullOrWhiteSpace(integrationLibsPath) && Directory.Exists(integrationLibsPath)) { probeDirs.Add(Path.GetFullPath(integrationLibsPath)); } if (Directory.Exists(applicationBasePath)) { probeDirs.Add(Path.GetFullPath(applicationBasePath)); } return new IntegrationLoadContext([.. probeDirs], logger); } }