natemcmaster / DotNetCorePlugins

.NET Core library for dynamically loading code

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[Question] What happened to AddDependencyContext?

netclectic opened this issue · comments

Previously with v1.4.0 I was using AddDependencyContext to help with finding dependencies by using the deps.json file associated with my plugin. This worked well for my use case.

However, after updating to v2 (2.0.0-beta.130) this method is no longer available. Is there some other way to achieve the same?

You can keep using 1.4. From a few bug reports I read (example #246), this will likely stop working with future versions of .NET, which is why I dropped that method from the 2.0 version of this library

Your comments seem to suggest that the built-in AssemblyDependencyResolver should pick up and handle any 'deps.json' files. This doesn't seem to be happening, do you know if there is something else I need to do for this to work?

I pulled a copy of the DependencyContextExtensions into my own project and that allowed me to continue to use AddDependencyContext, which seems to work ok with .Net 6. However, if I remove this then my dependencies are not resolved.

	string assemblyPath = Path.GetDirectoryName(Path.GetFullPath(assemblyFile));

	var context = new AssemblyLoadContextBuilder()
	   .SetMainAssemblyPath(assemblyFile)
	   .AddProbingPath(assemblyPath)
	   //.AddDependencyContext(Path.Combine(Path.GetDirectoryName(assemblyFile)!, $"{Path.GetFileNameWithoutExtension(assemblyFile)}.deps.json"))
	   .PreferDefaultLoadContext(true)
	   .PreferDefaultLoadContextAssembly(typeof(IServiceManifest).Assembly.GetName())
	   .Build();
	   
	using (context.EnterContextualReflection())
	{
		Assembly pluginDll = context.LoadFromAssemblyPath(assemblyFile);
		
		// attempt to validate the plugin implements the required interface
		// without AddDependencyContext this throws exception relating to not finding dependency
		var manifestType = Array.Find(pluginDll.GetTypes(), t => typeof(IServiceManifest).IsAssignableFrom(t));
	}

The docs say the built-in AssemblyDependencyResolver class loads a deps.json file.

The AssemblyDependencyResolver object is constructed with the path to a .NET class library. It resolves assemblies and native libraries to their relative paths based on the .deps.json file for the class library whose path was passed to the AssemblyDependencyResolver constructor.
https://learn.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support

Do you have a minimal project which reproduces the issue?

My setup is a little convoluted, but I put together a small test repo - https://github.com/netclectic/PluginTest

Essentially, the PluginDependency is built as a nuget package and referenced by the plugin Plugin1. The test console app and the plugin both reference the PluginShared where the plugin interface is defined.

I've included an Output directory that contains the pre-built plugin and plugin dependency (as it would appear in a local nuget repo).

The console app asks for the path to the plugin dll (in the Output dir) and attempts to load the type and create an instance of the plugin. This throws a dependency not found exception, however if you rebuild the console app with the call to AddDependencyContext uncommented back in then it works as expected.

Thanks for the repro. The problem seems to be that you prepared the plugin using dotnet pack or dotnet build. Use dotnet publish instead to collect all of your dependencies into the same place and 2.0.0-beta should work fine (tested myself)

Screenshot 2022-10-02 at 9 03 34 PM

Ok, that's kind of strange. That means the paths in the deps.json file are effectively ignored and every plugin has to be packaged with all of its dependencies locally.

Previously, it worked like a nuget repository, where the dependencies could be separate from the plugin, and it would find the correct version using the info in the deps.json file.

That seems like a step back, but at least I can work around it.

Thanks for your time.

I'm actually not sure how it worked for you in the first place. The repro you provided on a clean machine actually fails:

/private/tmp/PluginTest/Plugin1/Plugin1.csproj : error NU1101: Unable to find package PluginDependency. No packages exist with this id in source(s): nuget.org [/private/tmp/PluginTest/PluginTest.sln]

I can you see you saved some output in https://github.com/netclectic/PluginTest/tree/main/Output/Plugin. How did you prepare that?

The Plugin and PluginDependency are both deployed as nuget packages to a local nuget server, the Output folder is the package repository of the nuget server. The plugin references the dependency as a package, to get it to build you need to add a local a nuget source for the Output directory. I've updated the repo to add a nuget.config file that references the Output folder as a local source, so it should find the dependency package now.

The repo as is will fail during execution. However, if you uncomment this line - https://github.com/netclectic/PluginTest/blob/main/PluginTestConsole/Program.cs#L19 - to add back in the call to the missing AddDependencyContext (which is now included as part of the project) then it works.

Thanks for the information @netclectic. That makes more sense then. Running from the NuGet cache isn't something .NET supports out-of-the-box. Your project has made it work by parsing .deps.json and adding a new probing path that points to the NuGet cache.

I would still recommend using dotnet publish on plugins to collect dependencies. I personally think having fewer moving parts simplifies the system. But as you found, it's still possible to load binaries from the nuget cache by copying the old DependencyContextExtensions.cs file into your own project.