Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EnableDynamicLoading causes multiple loading of assemblies at runtime #41805

Open
G-MW opened this issue Jun 26, 2024 · 5 comments
Open

EnableDynamicLoading causes multiple loading of assemblies at runtime #41805

G-MW opened this issue Jun 26, 2024 · 5 comments
Labels
untriaged Request triage from a team member

Comments

@G-MW
Copy link

G-MW commented Jun 26, 2024

Describe the bug

Compile time

When adding <EnableDynamicLoading>true</EnableDynamicLoading> a copy of Microsoft.Extensions.DependencyInjection.Abstractions.dll is included in the output directory.

Runtime

At runtime (loading using the process described here) the assembly <output_directory>/Microsoft.Extensions.DependencyInjection.Abstractions.dll is loaded first and later another version of Microsoft.Extensions.DependencyInjection.Abstractions.dll is loaded from the dotnet installation directory. Then when trying to call the method BuildServiceProvider this exception is thrown:

Unhandled exception. System.MissingMethodException: Method not found: 'Microsoft.Extensions.DependencyInjection.ServiceProvider Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(Microsoft.Extensions.DependencyInjection.IServiceCollection)'.

Maybe this bug is related

List of Assemblies

These assemblies show this behaviour:

  • Microsoft.Extensions.DependencyInjection.Abstractions.dll
  • Microsoft.Extensions.Logging.Abstractions.dll

To Reproduce

  1. Create a new C# project containing:
<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFramework>net8.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <EnableDynamicLoading>true</EnableDynamicLoading>
    </PropertyGroup>

    <ItemGroup>
      <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
    </ItemGroup>

</Project>
  1. Build using dotnet build
  2. Now a copy of Microsoft.Extensions.DependencyInjection.Abstractions.dll is included in the output directory (usually <project_dir>/bin/Debug/net8.0)

Further technical details

Output of dotnet --info:

.NET SDK:
 Version:           8.0.302
 Commit:            ef14e02af8
 Workload version:  8.0.300-manifests.00402117
 MSBuild version:   17.10.4+10fbfbf2e

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.22621
 OS Platform: Windows
 RID:         win-x64
 Base Path:   C:\Program Files\dotnet\sdk\8.0.302\

.NET workloads installed:
There are no installed workloads to display.

Host:
  Version:      8.0.6
  Architecture: x64
  Commit:       3b8b000a0e

.NET SDKs installed:
  7.0.410 [C:\Program Files\dotnet\sdk]
  8.0.202 [C:\Program Files\dotnet\sdk]
  8.0.302 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 6.0.28 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 6.0.31 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 7.0.17 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 7.0.20 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 8.0.3 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 8.0.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 6.0.28 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 6.0.31 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 7.0.17 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 7.0.20 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 8.0.3 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 8.0.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.WindowsDesktop.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 6.0.28 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 6.0.31 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 7.0.17 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 7.0.20 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 8.0.3 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 8.0.6 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

Other architectures found:
  x86   [C:\Program Files (x86)\dotnet]
    registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x86\InstallLocation]

Environment variables:
  Not set

global.json file:
  Not found

Learn more:
  https://aka.ms/dotnet/info

Download .NET:
  https://aka.ms/dotnet/download

Workaround

Add this to your .csproj file to delete the assemblies from the output directory:

<Target Name="DeleteAssembliesFromOutoutPath" AfterTargets="PostBuildEvent">
	<Delete Files="$(OutputPath)\Microsoft.Extensions.DependencyInjection.Abstractions.dll" />
	<Delete Files="$(OutputPath)\Microsoft.Extensions.Logging.Abstractions.dll" />
</Target>
Copy link

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@dotnet-issue-labeler dotnet-issue-labeler bot added the untriaged Request triage from a team member label Jun 26, 2024
Copy link

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@vitek-karas
Copy link
Member

@elinor-fung

@elinor-fung
Copy link
Member

When adding <EnableDynamicLoading>true</EnableDynamicLoading> a copy of Microsoft.Extensions.DependencyInjection.Abstractions.dll is included in the output directory.

In the repro, this is expected. Microsoft.Extensions.DependencyInjection.Abstractions is part of the ASP.NET runtime, not the core runtime. Since the project uses Microsoft.NET.Sdk (as opposed to Microsoft.NET.Sdk.Web), the build determines that the library needs to carry Microsoft.Extensions.DependencyInjection.Abstractions with it. You can observe the same thing building an app (as in OutputType=Exe, not setting EnableDynamicLoading)

At runtime (loading using the process described here) the assembly <output_directory>/Microsoft.Extensions.DependencyInjection.Abstractions.dll is loaded first and later another version of Microsoft.Extensions.DependencyInjection.Abstractions.dll is loaded from the dotnet installation directory.

Are there other .NET components that get loaded by the process? Perhaps something that does depend on the ASP.NET runtime?

Using the load_assembly_and_get_function_pointer hosting option will load the assembly in a new, separate AssemblyLoadContext (ALC). My guess here is that <output_directory>/Microsoft.Extensions.DependencyInjection.Abstractions.dll is being loaded into that new ALC for your library, but something else causes Microsoft.Extensions.DependencyInjection.Abstractions.dll from the install to be loaded in the default ALC. And then the IServiceCollection is a different type in those two instances.

Is the MissingMethodException coming from a call your library makes? Do you also see Microsoft.Extensions.DependencyInjection.dll (which has the method called out in the exception you see) loaded from the install directory?

Add this to your .csproj file to delete the assemblies from the output directory:

If you are expecting those dependencies to be coming from a shared place (such that your library should not carry them with it), setting Private=false and ExcludeAssets=runtime on the PackageReference indicates that they should not be part of the library's output - this is what we recommend for plugins with shared dependencies.

<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0">
  <Private>false</Private>
  <ExcludeAssets>runtime</ExcludeAssets>
</PackageReference>

@elinor-fung
Copy link
Member

Using the load_assembly_and_get_function_pointer hosting option will load the assembly in a new, separate AssemblyLoadContext

If you don't actually want your library loaded into a separate ALC, we have an API in .NET 8+ that allows loading a component into the default ALC (and then calling a function on it). That would make it so that the library lives in the default ALC, so dependencies like Microsoft.Extensions.DependencyInjection.Abstractions wouldn't be loaded into two different ALCs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
untriaged Request triage from a team member
Projects
None yet
Development

No branches or pull requests

3 participants