Search code examples
.net-coreruntime.net-6.0search-pathassembly-loading

.NET Core application with multiple search paths


I am working on a complex migration to .NET Core 6 application which has a set of shared assemblies which are located in a different path than the main application assemblies due to our deployment strategy.

The libs are statically linked so I can't use AssemblyLoadContext or AppDomain to extend the resolving process, sadly. I saw that the runtimeconfig.json offers the possibility to add APP_PATHS which seem to work, BUT I only managed to use absolute paths - which is nonsense as I won't know the actual paths on the target machines.

Can't I use relative paths? Whenever I try even sub-paths I get an exception that the runtime cannot be created. Is there a magical syntax to that? It can't be so complicated to have the assemblies in multiple folders.... I feel stupid.

Has somebody an idea here? I can't just change the deployment structure / process.

Thanks

Update: After carefully subscribe to AssemblyLoadContext.Default and ensuring that only that happens in the static main, not some static constructors interfering, it works. Thanks for the heads up.


Solution

  • An example to my comment. I have three projects: Library A, Library B, and Application C.

    A.csproj and B.csproj

    <Project Sdk="Microsoft.NET.Sdk">
      <PropertyGroup>
        <TargetFramework>net8.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
      </PropertyGroup>
    </Project>
    

    ClassA in project A.

    using System.Runtime.CompilerServices;
    
    namespace A
    {
        public static class ClassA
        {
            [MethodImpl(MethodImplOptions.NoInlining)]
            public static int Plink()
            {
                return 1;
            }
        }
    }
    

    ClassB

    using System.Runtime.CompilerServices;
    
    namespace B
    {
        public class ClassB
        {
            [MethodImpl(MethodImplOptions.NoInlining)]
            public static int Plonk()
            {
                return 2;
            }
        }
    }
    

    C.csproj

    <Project Sdk="Microsoft.NET.Sdk">
      <PropertyGroup>
        <OutputType>exe</OutputType>
        <TargetFrameworks>net8.0</TargetFrameworks>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
      </PropertyGroup>
      <ItemGroup>
        <ProjectReference Include="..\A\A.csproj" Private="false" />
        <ProjectReference Include="..\B\B.csproj" Private="false" />
      </ItemGroup>
    </Project>
    

    Program.cs

    
    using System.Runtime.CompilerServices;
    using System.Runtime.Loader;
    
    using A;
    using B;
    
    namespace C
    {
        public static class Program
        {
            public static void Main(string[] args)
            {
                SetupAssemblies();
                Foo();
            }
    
            private static void SetupAssemblies()
            {
                AssemblyLoadContext.Default.Resolving += Default_Resolving;
            }
    
            private static System.Reflection.Assembly? Default_Resolving(AssemblyLoadContext arg1, System.Reflection.AssemblyName arg2)
            {
                Console.WriteLine($"Attempting to resolve {arg2}");
    
                string assemblyPath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), $@"..\deps\{arg2.Name}.dll"));
                return AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
            }
    
            [MethodImpl(MethodImplOptions.NoInlining)]
            private static void Foo()
            {
                Console.WriteLine(ClassA.Plink() + ClassB.Plonk());
            }
        }
    }
    

    I structured my output as follows

    ~\Projects\SO\C\BIN\DEBUG
    │
    ├───deps
    │       A.dll
    │       B.dll
    └───net8.0
            C.deps.json
            C.dll
            C.exe
            C.pdb
            C.runtimeconfig.json
    

    Program output:

    Attempting to resolve A, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
    Attempting to resolve B, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
    3