.NET Core application with multiple search paths

81 views Asked by At

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.

1

There are 1 answers

2
Hank On BEST ANSWER

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