File: IntegrationLoadContext.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.Logging;
 
namespace Aspire.Hosting.RemoteHost;
 
/// <summary>
/// Loads Aspire integration assemblies from probe directories with version unification.
/// If the default load context already provides an assembly at a higher or equal version,
/// the default context wins. Only <c>Aspire.TypeSystem</c> is always shared.
/// </summary>
internal sealed class IntegrationLoadContext : AssemblyLoadContext
{
    private const string SharedAssemblyName = "Aspire.TypeSystem";
 
    private readonly string[] _probeDirectories;
    private readonly ILogger _logger;
 
    internal IntegrationLoadContext(string[] probeDirectories, ILogger logger)
        : base("Aspire.Integrations")
    {
        _probeDirectories = probeDirectories;
        _logger = logger;
    }
 
    protected override Assembly? Load(AssemblyName assemblyName)
    {
        if (assemblyName.Name is null ||
            string.Equals(assemblyName.Name, SharedAssemblyName, StringComparison.OrdinalIgnoreCase))
        {
            // Returning null tells the runtime to fall back to the default ALC.
            // This ensures Aspire.TypeSystem has the same type identity in both
            // contexts, so shared contracts (ICodeGenerator, ILanguageSupport,
            // AtsContext, etc.) work across the ALC boundary without requiring
            // reflection or marshalling.
            return null;
        }
 
        // Find the assembly in probe directories
        string? probedPath = null;
        foreach (var dir in _probeDirectories)
        {
            var path = Path.Combine(dir, $"{assemblyName.Name}.dll");
            if (File.Exists(path))
            {
                probedPath = path;
                break;
            }
        }
 
        if (probedPath is null)
        {
            return null;
        }
 
        // Version unification: if the default context already has this assembly
        // at a higher or equal version (e.g., framework-provided), defer to it.
        if (TryGetDefaultContextVersion(assemblyName, out var defaultVersion))
        {
            var probedVersion = AssemblyName.GetAssemblyName(probedPath).Version;
            if (defaultVersion >= probedVersion)
            {
                _logger.LogDebug("[IntegrationALC] Deferring to default ({DefaultVersion} >= {ProbedVersion}): {AssemblyName}",
                    defaultVersion, probedVersion, assemblyName.Name);
                return null;
            }
        }
 
        _logger.LogDebug("[IntegrationALC] Loading: {AssemblyName} from {Path}", assemblyName.Name, probedPath);
        return LoadFromAssemblyPath(probedPath);
    }
 
    private static bool TryGetDefaultContextVersion(AssemblyName assemblyName, out Version? version)
    {
        version = null;
        try
        {
            var defaultAsm = Default.LoadFromAssemblyName(assemblyName);
            version = defaultAsm.GetName().Version;
            return version is not null;
        }
        catch
        {
            return false;
        }
    }
}