C
C#2mo ago
Sky

Choosing what architecture NuGet.Protocol Downloads

Hi there! I'm working on a sort of addon-like system for one of my project, and would like to handle addon's dependencies using NuGet repositories. Right now, I'm downloading the DLLs requested by an addon (for instance in my test, it was SpacedGrid-Avalonia version 11.0.0) into a folder, then loading the DLLs using Assembly#Load. However, when calling this method, I'm getting a Bad IL Format, which I guess means the downloaded DLLs was not for the right machine arch. Here's my code for downloading from NuGet repos:
using var packageStream = new MemoryStream();

await resource.CopyNupkgToStreamAsync(
dependencyName, latestVersion,
packageStream, cache,
NullLogger.Instance, default);

var folder = Path.Combine(AppConfig.AppDataFolderPath, "Addons", "Dependencies");
if (!Directory.Exists(folder)) Directory.CreateDirectory(folder);

var filePath = Path.Combine(folder, $"{dependencyName}_{latestVersion}.dll");
await File.WriteAllBytesAsync(filePath, packageStream.ToArray());
using var packageStream = new MemoryStream();

await resource.CopyNupkgToStreamAsync(
dependencyName, latestVersion,
packageStream, cache,
NullLogger.Instance, default);

var folder = Path.Combine(AppConfig.AppDataFolderPath, "Addons", "Dependencies");
if (!Directory.Exists(folder)) Directory.CreateDirectory(folder);

var filePath = Path.Combine(folder, $"{dependencyName}_{latestVersion}.dll");
await File.WriteAllBytesAsync(filePath, packageStream.ToArray());
75 Replies
reflectronic
reflectronic2mo ago
what exactly do you do with the file that you write?
Sky
Sky2mo ago
I load it using Assembly.Load
reflectronic
reflectronic2mo ago
yes, that will not work a nupkg isn't an assembly
Sky
Sky2mo ago
I also tried LoadFromFile, but it stills throw me the "Bad IL"
reflectronic
reflectronic2mo ago
it's a zip file
Sky
Sky2mo ago
well the file the code downloads is a dll oh I think I get what you mean Ohh okay I am very stupid
reflectronic
reflectronic2mo ago
i can assure you CopyNupkgToStreamAsync does not download a DLL. i mean, it says in the name, it downloads a nupkg if you open that file in a program like 7-zip you will see it has some directories and a bunch of files in it
Sky
Sky2mo ago
yup sorry, I convinced myself it was a dll after looking at the downlaoded file 😭
reflectronic
reflectronic2mo ago
one (or even more) of those files is a DLL it is somewhat difficult to select the right DLL from the nupkg
Sky
Sky2mo ago
So once downloaded, I have to unpack the Nupkg file right? ah, because of the different frameworks?
reflectronic
reflectronic2mo ago
yes, mainly i am pretty sure there are APIs in the nuget client to select the right DLL, though i don't know what they are
Sky
Sky2mo ago
Gotcha, I'll dig into this more now I'm getting the error 😂 Thanks for the help :Lovely:
reflectronic
reflectronic2mo ago
i think https://github.com/waf/CSharpRepl/blob/main/CSharpRepl.Services/Nuget/NugetPackageInstaller.cs is a good example of what needs to be done, the code is kinda complicated, but you can see what it does starting from "InstallAsync" the implementation of GetRuntimeGraph is kinda shitty, i would not have done it that way, it might not work for you. otherwise, the code is pretty easily adapted to whatever you need
Sky
Sky2mo ago
Oki! I got it working, loading the DLL is no longer a problem However, I am doing the "same thing" (I mean loading a dll, and this time I'm sure it's one lmao) for my addon system, however even if I load the dependency before the addon's dll, it cannot found the dependency's DLL Tried with the exact same version as well (of the dependency I mean) and nope, I'm getting the whole System.IO.FileNotFoundException: Could not load file or assembly 'SpacedGridControl.Avalonia, Version=11.0.0.2, Culture=neutral, PublicKeyToken=null'. Le fichier spécifié est introuvable.
reflectronic
reflectronic2mo ago
what are you passing to Assembly.Load to be clear, it does not take a file path
Sky
Sky2mo ago
for loading the dep or addon? yea I'm passing a stream iirc
var dependencyPath = Path.Combine(AppConfig.AppDataFolderPath, "Addons", "Dependencies", $"{dependencyName}_{versionToLoad}.dll");
SkEditorAPI.Logs.Debug("Loading dependency: " + dependencyPath + " version: " + versionToLoad + "...");
try
{
Assembly.LoadFile(dependencyPath);
}
catch (Exception e)
{
SkEditorAPI.Logs.Error($"Failed to load dependency {dependencyName} from {dependencyPath}: {e.Message}");
addonMeta.Errors.Add(LoadingErrors.FailedToLoadDependency(dependencyName, e.Message));
return false;
}
var dependencyPath = Path.Combine(AppConfig.AppDataFolderPath, "Addons", "Dependencies", $"{dependencyName}_{versionToLoad}.dll");
SkEditorAPI.Logs.Debug("Loading dependency: " + dependencyPath + " version: " + versionToLoad + "...");
try
{
Assembly.LoadFile(dependencyPath);
}
catch (Exception e)
{
SkEditorAPI.Logs.Error($"Failed to load dependency {dependencyName} from {dependencyPath}: {e.Message}");
addonMeta.Errors.Add(LoadingErrors.FailedToLoadDependency(dependencyName, e.Message));
return false;
}
That's for the dependency (using LoadFile instead to see if it changed anything but nope) and yea for addon I'm just passing a stream Assembly.Load(await File.ReadAllBytesAsync(addonDllFile))
reflectronic
reflectronic2mo ago
you will have to explain more about this plugin system are you using AssemblyLoadContext?
Sky
Sky2mo ago
not at all should I? x)
reflectronic
reflectronic2mo ago
yes, probably
reflectronic
reflectronic2mo ago
Create a .NET Core application with plugins - .NET
Learn how to create a .NET Core application that supports plugins.
reflectronic
reflectronic2mo ago
AssemblyLoadContext allows the plugins to not conflict with each other
Sky
Sky2mo ago
owh sweet thanks 👍
reflectronic
reflectronic2mo ago
and, i am not really sure what you need this nuget thing for--if it's just because your pluigin depends on this library, i think the article proposes a much easier way to do it
Sky
Sky2mo ago
well it's to let addons use dependecies that are not in my program and as dlls cannot be "combined" I though it was the only way
reflectronic
reflectronic2mo ago
they cannot be combined, the way the article proposes is to use a folder instead the folder contains the plugin and all of the DLLs it uses and there is a .deps.json file which will tell the plugin loader exactly what is what
Sky
Sky2mo ago
so if people (normal users I mean) wanna add a plugin, they'll have to put a whole folder? well a zip technically
reflectronic
reflectronic2mo ago
in case the plugin has dependencies, yes, downloading a zip and extracting it as a folder somewhere would be the way to do it i think it is going to be much easier and less problematic than trying to implement some kind of dependency loader yourself
Sky
Sky2mo ago
Hum well actually I'll end up with the same problem cuz at the end in both situation I have DLLs to load (dependencies I mean)
reflectronic
reflectronic2mo ago
yes, but, the API handles it for you https://learn.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support#load-plugins the AssemblyDependencyResolver thing finds the dependency DLLs which are next to the plugin
Sky
Sky2mo ago
Ohh
reflectronic
reflectronic2mo ago
so, when the plugin tries to find SpacedGridControl.Avalonia (for example), it calls into the PluginLoadContext object you made for that plugin it calls your override of Load, and you can do any logic you want in there
Sky
Sky2mo ago
Oki, I reworked my logic behind, but I still have the same error
Sky
Sky2mo ago
No description
Sky
Sky2mo ago
Now both the addon & dep are in the same folder
var folder = Path.Combine(AppConfig.AppDataFolderPath, "Addons");
var folders = Directory.GetDirectories(folder);
var dllFiles = new List<string>();
foreach (string sub in folders)
dllFiles.Add(Path.Combine(sub, Path.GetFileName(sub) + ".dll"));
var folder = Path.Combine(AppConfig.AppDataFolderPath, "Addons");
var folders = Directory.GetDirectories(folder);
var dllFiles = new List<string>();
foreach (string sub in folders)
dllFiles.Add(Path.Combine(sub, Path.GetFileName(sub) + ".dll"));
Here's how I gather the addon's dll files And for each, I'm loading it this way:
AddonLoadContext loadContext = new AddonLoadContext(Path.GetFullPath(addonDllFile));
addon = loadContext.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(addonDllFile)))
.GetTypes()
.Where(p => typeof(IAddon).IsAssignableFrom(p) && p is { IsClass: true, IsAbstract: false })
.Select(addonType => (IAddon)Activator.CreateInstance(addonType))
.ToList();
AddonLoadContext loadContext = new AddonLoadContext(Path.GetFullPath(addonDllFile));
addon = loadContext.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(addonDllFile)))
.GetTypes()
.Where(p => typeof(IAddon).IsAssignableFrom(p) && p is { IsClass: true, IsAbstract: false })
.Select(addonType => (IAddon)Activator.CreateInstance(addonType))
.ToList();
public class AddonLoadContext : AssemblyLoadContext
{

private readonly AssemblyDependencyResolver _resolver;

public AddonLoadContext(string pluginPath)
{
_resolver = new AssemblyDependencyResolver(pluginPath);
}

protected override Assembly Load(AssemblyName assemblyName)
{
var assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
return assemblyPath != null ? LoadFromAssemblyPath(assemblyPath) : null;
}

protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
var libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
return libraryPath != null ? LoadUnmanagedDllFromPath(libraryPath) : IntPtr.Zero;
}

}
public class AddonLoadContext : AssemblyLoadContext
{

private readonly AssemblyDependencyResolver _resolver;

public AddonLoadContext(string pluginPath)
{
_resolver = new AssemblyDependencyResolver(pluginPath);
}

protected override Assembly Load(AssemblyName assemblyName)
{
var assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
return assemblyPath != null ? LoadFromAssemblyPath(assemblyPath) : null;
}

protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
var libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
return libraryPath != null ? LoadUnmanagedDllFromPath(libraryPath) : IntPtr.Zero;
}

}
I'm not sure if I did something wrong this time x)
reflectronic
reflectronic2mo ago
you need to build the addon so that it copies its dependencies and generates the dependency file so, in your SdkEditorTestAddon.dll, you need to add this to your csproj:
<EnableDynamicLoading>true</EnableDynamicLoading>
<EnableDynamicLoading>true</EnableDynamicLoading>
then, try building it again, and look at the build output (it might not be fully right, so, can you send a picture of it)
Sky
Sky2mo ago
well the problem is that he also generates dll for libs that are already in my app
Sky
Sky2mo ago
No description
reflectronic
reflectronic2mo ago
ok, so, you have <ProjectReference Include="MyApp.csproj" /> somewhere in ther change it to <ProjectReference Include="MyApp.csproj" Private="false" ExcludeAssets="runtime" /> this will tell your plugin to not depend on any of the app's libaries even though you have referenced it
Sky
Sky2mo ago
you'll beat me, but I was referencing each deps ;-;
reflectronic
reflectronic2mo ago
what do you mean no, it's OK that you use the libraries in the plugin it just will not copy them to the build output sorry i was not clear
Sky
Sky2mo ago
No description
Sky
Sky2mo ago
I got this for all other libs, referencing their DLLs
reflectronic
reflectronic2mo ago
oh is there a reason you are not doing ProjectReference instead
Sky
Sky2mo ago
Because I am stupid :blobsweat: Although it's been a solid time I code with C#, its whole env is a bit new to me while I'm used to Gradle with Java, so it's even harder to move on
Sky
Sky2mo ago
Okay, using the project reference i'm getting some unresolved deps from the addon
No description
reflectronic
reflectronic2mo ago
uh, would you mind showing the csproj file you have for the main program
Sky
Sky2mo ago
yessir
Sky
Sky2mo ago
Thanks for the time you're taking to help be btw :love:
reflectronic
reflectronic2mo ago
if you try doing dotnet build on the plugin does it have the same errors
Sky
Sky2mo ago
huh, nope, all I get is warnings but I think it got built correctly
reflectronic
reflectronic2mo ago
maybe try restarting rider
Sky
Sky2mo ago
WHAT all the errors from Rider disapeared 🤣
reflectronic
reflectronic2mo ago
it is probably just a little bit confused
Sky
Sky2mo ago
ahh the csproj got restored, it's maybe beacsue of that? the app's one I mean
reflectronic
reflectronic2mo ago
yeah that is probably what happened
Sky
Sky2mo ago
ahh sorry, didn't think about it :blobsweat:
reflectronic
reflectronic2mo ago
ok, anyway, when you look at the build output for the plugin, what does it look like
Sky
Sky2mo ago
No description
Sky
Sky2mo ago
I'm getting all of this
reflectronic
reflectronic2mo ago
hm. this is a somewhat unpleasant situation
Sky
Sky2mo ago
Oh :pandaSob:
reflectronic
reflectronic2mo ago
does adding <PackageReference Include="Avalonia" Version="11.0.0" ExcludeAssets="runtime" /> remove all of the avalonia DLLs
Sky
Sky2mo ago
yup, got a lot less
No description
reflectronic
reflectronic2mo ago
you will have to add
<PackageReference Include="Avalonia" Version="11.0.10" ExcludeAssets="runtime" />
<PackageReference Include="MicroCom.Runtime" Version="0.11.0" ExcludeAssets="runtime" />
<PackageReference Include="Avalonia.Remote.Protocol" Version="11.0.10" ExcludeAssets="runtime" />
<PackageReference Include="Avalonia" Version="11.0.10" ExcludeAssets="runtime" />
<PackageReference Include="MicroCom.Runtime" Version="0.11.0" ExcludeAssets="runtime" />
<PackageReference Include="Avalonia.Remote.Protocol" Version="11.0.10" ExcludeAssets="runtime" />
to your project file to remove all of the avalonia DLLs that shouldn't be there unfortunately i don't think there is a good way to do it automatically
Sky
Sky2mo ago
Got it In my case rn I'm using local things, but I plan to put my project on nuget for easier use will those still be required? yup that worked btw Thanks again!
reflectronic
reflectronic2mo ago
i do not think it is needed--i think the DLLs will just be ignored if they are there the problem is that, when you reference SpacedGridControl.Avalonia, it thinks you also need all of Avalonia but you do not, because it comes with the plugin host so, it will copy the DLLs. they will be ignored, because it will always choose the DLLs from the plugin host anyway. so, it doesn't cause any harm. but it is kind of unclean. and writing ExcludeAssets is the only way to fix it currentlly
Sky
Sky2mo ago
ohh oki I see sort of conflicts from two sources
reflectronic
reflectronic2mo ago
yes anyway, now you can copy that build output folder to the folder for the plugin and it should just work
Sky
Sky2mo ago
Yup it worked like a charm! Thanks you very much 👍
Sky
Sky2mo ago
welp, me again, I think I'm too much used to Java's generic types, but I am getting something wrong here? :thonk:
No description
No description
reflectronic
reflectronic2mo ago
yes, it is not possible even if you declared ISettingType<out T> using out because bool is a value type, variance conversions do not work
Sky
Sky2mo ago
There's nothing similar to ? for generic types in C#?
reflectronic
reflectronic2mo ago
no, there are no wildcards
Sky
Sky2mo ago
hum got it, thanks 👍 I think i'll just use object in this case i mean without any generic type xD