title: Failed to load plugin dependency using AssemblyLoadContext in .NET Core 3.0
body: <p>I have a plugin with failing dependencies though I tried to follow as much MS recommendation on .NET Core 3.0. Been trying to figure out whats the problem for 2 days already :/. Appreciate an input or where to look. Thanks!</p>
  
  <p>Details:</p>
  
  <ul>
  <li>Entry point is a CLI app published by .NET Core 3.0 self-contained app
  
  <pre><code>//publishes as EXE file
  dotnet publish -c release -r win-x64 /p:publishsinglefile=true /p:publishtrimmed=true
  </code></pre></li>
  <li>Testing on Win-x64 and Linux-x64</li>
  <li>.NET Core 3.0 assemblies</li>
  <li><p>Plugin DLLs are published with this command</p>
  
  <pre><code>//publishes as DLL files
  dotnet publish -c release -r linux-x64 /p:publishtrimmed=true -o ./app
  </code></pre></li>
  </ul>
  
  <p>The logs shows that file is there, it exists but the dependency resolver failed to load it and throws a FileNotFoundException. Strange.</p>
  
  <pre><code>
  C:\play\yuniql\yuniql-cli\bin\release\netcoreapp3.0\win-x64\publish&gt;yuniql.exe run --plugins-path C:\temp\aztools\yuniql\0.0.0\x64\.plugins -p C:\play\yuniql\yuniql-plugins\postgresql\samples -c "Host=localhost;Port=5432;Username=xxx;Password=xxx;Database=yuniqldb" --platform postgresql -a 1 --debug
  ...
  DBG   platformLowerCased: postgresql
  DBG   pluginsPathParameter: C:\temp\aztools\yuniql\0.0.0\x64\.plugins
  DBG   defaultAssemblyBasePath: C:\temp\aztools\yuniql\0.0.0\x64\.plugins\postgresql
  ...
  DBG   Found plugin dir: C:\temp\aztools\yuniql\0.0.0\x64\.plugins\postgresql
  DBG   Found plugin file: C:\temp\aztools\yuniql\0.0.0\x64\.plugins\postgresql\Npgsql.dll. ProductVersion: 4.1.1, FileVersion: 4.1.1.0
  DBG   Found plugin file: C:\temp\aztools\yuniql\0.0.0\x64\.plugins\postgresql\Yuniql.Extensibility.dll. ProductVersion: 0.302.0, FileVersion: 0.302.0.0
  DBG   Found plugin file: C:\temp\aztools\yuniql\0.0.0\x64\.plugins\postgresql\Yuniql.PostgreSql.deps.json. ProductVersion: , FileVersion:
  DBG   Found plugin file: C:\temp\aztools\yuniql\0.0.0\x64\.plugins\postgresql\Yuniql.PostgreSql.dll. ProductVersion: 0.305.0, FileVersion: 0.305.0.0
  DBG   Found plugin file: C:\temp\aztools\yuniql\0.0.0\x64\.plugins\postgresql\Yuniql.PostgreSql.pdb. ProductVersion: , FileVersion:
  
  DBG   pluginAssemblyFilePath: C:\temp\aztools\yuniql\0.0.0\x64\.plugins\postgresql\Yuniql.PostgreSql.dll
  DBG   loadedAssembly: Yuniql.PostgreSql, Version=0.305.0.0, Culture=neutral, PublicKeyToken=null
  DBG   retryResolving: Npgsql, Version=4.1.1.0, Culture=neutral, PublicKeyToken=5d8b90d52f46fda7
  DBG   failedAssemblyFileExists: True
  DBG   attempting to reload Npgsql, Version=4.1.1.0, Culture=neutral, PublicKeyToken=5d8b90d52f46fda7 via streaming from C:\temp\aztools\yuniql\0.0.0\x64\.plugins\postgresql\Npgsql.dll
  
  ERR   Failed to execute run function. Target database will be rolled back to its previous state.
  System.IO.FileNotFoundException: Could not load file or assembly 'Npgsql, Version=4.1.1.0, Culture=neutral, PublicKeyToken=5d8b90d52f46fda7'. The system cannot find the file specified.
  File name: 'Npgsql, Version=4.1.1.0, Culture=neutral, PublicKeyToken=5d8b90d52f46fda7'
     at Yuniql.PostgreSql.PostgreSqlDataService.GetConnectionInfo()
     at Yuniql.Core.MigrationService.Run(String workingPath, String targetVersion, Boolean autoCreateDatabase, List`1 tokenKeyPairs, Boolean verifyOnly, String delimeter)
     at Yuniql.CLI.CommandLineService.RunMigration(RunOption opts)
  
  Unhandled exception. System.IO.FileNotFoundException: Could not load file or assembly 'Npgsql, Version=4.1.1.0, Culture=neutral, PublicKeyToken=5d8b90d52f46fda7'. The system cannot find the file specified.
  File name: 'Npgsql, Version=4.1.1.0, Culture=neutral, PublicKeyToken=5d8b90d52f46fda7'
     at Yuniql.PostgreSql.PostgreSqlDataService.GetConnectionInfo()
     at Yuniql.Core.MigrationService.Run(String workingPath, String targetVersion, Boolean autoCreateDatabase, List`1 tokenKeyPairs, Boolean verifyOnly, String delimeter)
     at Yuniql.CLI.CommandLineService.RunMigration(RunOption opts)
     at Yuniql.Program.&lt;&gt;c__DisplayClass0_0.&lt;Main&gt;b__1(RunOption opts)
     at CommandLine.ParserResultExtensions.MapResult[T1,T2,T3,T4,T5,T6,T7,T8,TResult](ParserResult`1 result, Func`2 parsedFunc1, Func`2 parsedFunc2, Func`2 parsedFunc3, Func`2 parsedFunc4, Func`2 parsedFunc5, Func`2 parsedFunc6, Func`2 parsedFunc7, Func`2 parsedFunc8, Func`2 notParsedFunc)
     at Yuniql.Program.Main(String[] args)
  C:\play\yuniql\yuniql-cli\bin\release\netcoreapp3.0\win-x64\publish&gt;
  </code></pre>
  
  <p>PluginAssemblyLoadContext
  <a href="https://github.com/rdagumampan/yuniql/blob/master/yuniql-core/PluginAssemblyLoadContext.cs" rel="noreferrer">https://github.com/rdagumampan/yuniql/blob/master/yuniql-core/PluginAssemblyLoadContext.cs</a></p>
  
  <pre class="lang-cs prettyprint-override"><code>class PluginAssemblyLoadContext : AssemblyLoadContext
  {
      private AssemblyDependencyResolver _resolver;
  
      public PluginAssemblyLoadContext(string pluginPath) : base(isCollectible: true)
      {
          _resolver = new AssemblyDependencyResolver(pluginPath);
      }
      protected override Assembly Load(AssemblyName name)
      {
          string assemblyPath = _resolver.ResolveAssemblyToPath(name);
          if (assemblyPath != null)
          {
              Console.WriteLine($"Loading assembly {assemblyPath} into the HostAssemblyLoadContext");
              return LoadFromAssemblyPath(assemblyPath);
          }
  
          return null;
      }
  }
  </code></pre>
  
  <p>Load plugin from /app/.plugins/PostgreSql
  <a href="https://github.com/rdagumampan/yuniql/blob/master/yuniql-core/MigrationServiceFactory.cs" rel="noreferrer">https://github.com/rdagumampan/yuniql/blob/master/yuniql-core/MigrationServiceFactory.cs</a></p>
  
  <pre class="lang-cs prettyprint-override"><code>// create the unloadable HostAssemblyLoadContext
  var pluginAssemblyLoadContext = new PluginAssemblyLoadContext(pluginAssemblyBasePath);
  
  //the plugin assembly into the HostAssemblyLoadContext. 
  //the assemblyPath must be an absolute path.
  var assembly = pluginAssemblyLoadContext.LoadFromAssemblyPath(pluginAssemblyFilePath);
  pluginAssemblyLoadContext.Assemblies
      .ToList()
      .ForEach(a =&gt;
      {
          _traceService.Debug($"loadedAssembly: {a.FullName}");
      });
  pluginAssemblyLoadContext.Resolving += AssemblyContext_Resolving;
  pluginAssemblyLoadContext.Unloading += AssemblyContext_Unloading;
  
  var sqlDataService = assembly.GetTypes()
      .Where(t =&gt; t.Name.ToLower().Contains($"{platformLowerCased.ToLower()}dataservice"))
      .Select(t =&gt; Activator.CreateInstance(t, _traceService))
      .Cast&lt;IDataService&gt;()
      .First();
  
  var bulkImportService = assembly.GetTypes()
      .Where(t =&gt; t.Name.ToLower().Contains($"{platformLowerCased.ToLower()}bulkimportservice"))
      .Select(t =&gt; Activator.CreateInstance(t, _traceService))
      .Cast&lt;IBulkImportService&gt;()
      .First();
  
  var tokenReplacementService = new TokenReplacementService(_traceService);
  
  var migrationService = new MigrationService(sqlDataService, bulkImportService, tokenReplacementService, directoryService, fileService, _traceService);
  return migrationService;
  </code></pre>
  
  <p>Attempt to force resolve</p>
  
  <pre class="lang-cs prettyprint-override"><code>private System.Reflection.Assembly AssemblyContext_Resolving(System.Runtime.Loader.AssemblyLoadContext assemblyContext, System.Reflection.AssemblyName failedAssembly)
  {
      var pluginAssemblyLoadContext = assemblyContext as PluginAssemblyLoadContext;
      _traceService.Debug($"retryResolving: {failedAssembly.FullName}");
  
      var assemblyFilePath = Path.Combine(pluginAssemblyLoadContext.PluginPath, failedAssembly.Name + ".dll");
      _traceService.Debug($"failedAssemblyFileExists: {File.Exists(assemblyFilePath)}");
  
      using (var file = File.Open(assemblyFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
      {
          _traceService.Debug($"attempting to reload {failedAssembly.FullName} via streaming from {assemblyFilePath}");
          pluginAssemblyLoadContext.LoadFromStream(file);
      }
  
      //assemblyContext.LoadFromAssemblyPath(assemblyFilePath);
      //assemblyContext.LoadFromAssemblyName(new System.Reflection.AssemblyName(failedAssembly.Name));
  
      return null;
  }
  </code></pre>
  
  <p>UPDATE 12/26/2019: I thought it was an issue only to linux but I was able to reproduce in Windows. Interestingly, when I run my platform tests which calls the Factory class directly but still the factory uses the AssemblyLoadContext, it works OK!.</p>
  
  <p>The difference, it fails when called by CLI (a .NET Core 3.0 selfcontained EXE app) but OK when called from VSTestRunner (MSTEST).</p>