Groups keyboard shortcuts have been updated
Dismiss
See shortcuts

Assembly Load Context Issues AspNetCore

81 views
Skip to first unread message

Steven Lovelock

unread,
Nov 14, 2024, 3:13:57 PM11/14/24
to Excel-DNA
Hi Govert

I am trying to run a small .net 8.0 kestrel sever from an Excel Dna addin. I can get it to successfully run, but there are a lot of issues related to which assembly load context the asp net core components are loaded into. The asp net core hosting seems to get loaded into the Default ALC, which are then not able to discover any controllers in my assembly. It is possible to work around this specific issue by explicitly adding the loaded assembly and disabling the auto load (the commented code in AutoOpen).

Microsoft.AspNetCore.Hosting.Diagnostics: Critical: Hosting startup assembly exception

System.InvalidOperationException: Startup assembly ExcelDnaAspNetCore failed to execute. See the inner exception for more details.
 ---> System.IO.FileNotFoundException: Could not load file or assembly 'ExcelDnaAspNetCore, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
...

There are also then further issues if direct or transient reference targets a newer version of framework assemblies (e.g. System.Text.Json 8.0.5 which isn't vulnerable). In my example you can make this occur by uncommenting the reference to Logging.Abstractions. This makes a copy of the newer DLL appear in the bin folder which the ExcelDnaAssemblyLoadContext will load. You can see in the Debug->Modules window that 2 different versions of Logging.Abstractions are loaded (the ref assembly in the Default ALC, and the bin's assembly in the ExcelDnaAssemblyLoadContext ALC). I can also work around this by explicitly adding a PackageReference with ExcludedAssets=all (or explicitly downgrading them) to avoid the DLLs being copied to the bin, but it is hard to know exactly which transitive references may end up being used by referenced projects. Also depending on the SDK version installed, it might not copy the DLL to the bin folder if you have a new enough patched version. E.g. donet SDK 8.0.7 copied System.Text.Json 8.0.5 to the bin, but 8.0.10 did not.

To prevent this from accidentally occurring, I might have to add in a custom msbuild target to detect when a reference assembly ends up copied to the bin folder and fail the build.

Is there a better way of getting all of this to work? I would have ideally liked one of the following:
My minimal example is as follows:
ExcelDnaAspNetCore.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net8.0-windows</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
<ExcelAddInCustomRuntimeConfiguration>true</ExcelAddInCustomRuntimeConfiguration>
<UseWindowsForms>true</UseWindowsForms>
  </PropertyGroup>

  <ItemGroup>
<PackageReference Include="ExcelDna.AddIn" Version="1.8.0" />
<!-- This could have come from a referenced dependency -->
<!-- Uncomment for MissingMethodException -->
<!--<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.2" />-->
  </ItemGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
</Project>


Addin.cs
using ExcelDna.Integration;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Logging;
using System.Runtime.Loader;
using System.Text.Json;
using Microsoft.AspNetCore.Mvc;
using ExcelDna.Logging;

namespace ExcelDnaAspNetCore;

public class Addin : IExcelAddIn
{
    public void AutoOpen()
    {
        LogDisplay.Show();
        var host = Host.CreateDefaultBuilder()
            .ConfigureLogging(l => l.AddDebug())
            .ConfigureServices((h, s) =>
            {
                s.AddHttpClient();
                s.AddControllers()
                    //.AddApplicationPart(typeof(Addin).Assembly) // workaround to find controllers in this loaded assembly
                ;
            })
            .ConfigureWebHostDefaults(web =>
            {
                // https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/platform-specific-configuration?view=aspnetcore-8.0#disable-automatic-loading-of-hosting-startup-assemblies
                //web.UseSetting(WebHostDefaults.PreventHostingStartupKey, "true"); // workaround to not 'Assembly.Load(this assembly name)' from GenericWebHostBuilder.cs
                web.ConfigureKestrel(options =>
                {
                    options.ListenLocalhost(9999);
                });
                web.Configure(app =>
                {
                    app.UseRouting();
                    app.UseEndpoints(endpoints =>
                    {
                        endpoints.MapControllers();
                        endpoints.MapGet("/", () => JsonSerializer.Serialize("hello world!"));
                    });
                });
            })
            .Start();
        var logger = host.Services.GetRequiredService<ILogger<Addin>>();
        logger.LogInformation("Loaded");

        foreach (var alc in AssemblyLoadContext.All)
        {
            logger.LogInformation("### {Name} ###", alc.Name);
            foreach (var assembly in alc.Assemblies)
                logger.LogInformation("- {Name,-60}{Location}", assembly.GetName().Name, assembly.Location);
        }
    }
    public void AutoClose() { }
}

[ApiController]
[Route("api/[controller]")]
public class TestController
{
    [HttpGet]
    public string Get() => "hello world";
}


Regards
Steven

Govert van Drimmelen

unread,
Nov 19, 2024, 1:21:34 PM11/19/24
to Excel-DNA
Hi Steven,

Thank you for making such a detailed post of your issue.
I think you are exploring the outer limits of the .NET core isolation support.

I'd expect the AspNet system assemblies to load into the default ALC, and think explicitly adding your assembly for probing is appropriate.
But I don't know how to ensure that subsequent assemblies are loaded into your ALC correctly.
Having some assemblies loaded into both the default and add-in ALC should not be a problem, though I might misunderstand how types are then resolved across the ALCs.

I get the feeling that the dotnet developers do not consider the shared-process ALC-based isolation a high priority scenario.

Please post back any other information you might discover.
I'll also look out for more on this topic.

-Govert

Govert van Drimmelen

unread,
Nov 20, 2024, 6:47:35 AM11/20/24
to Excel-DNA
Hi Steven,

I want to add a reference to this issue in the AspNetCore repository:  

I didn't think of that initially, but you should definitely experiment with `EnterContextualReflection()` around the code region where the AspNet libraries will load your dependencies.
This call ensures that chained assembly loads stay inside the ALC. You'll find some other examples where this call might have helped in these discussions:

-Govert

Steven Lovelock

unread,
Nov 21, 2024, 5:19:56 AM11/21/24
to Excel-DNA
Hi Govert

Thanks for the reply. I will try experimenting with that EnterContextualReflection as I haven't used it before.
I'll let you know if I make any useful progress.

Steven
Reply all
Reply to author
Forward
0 new messages