Last active
March 18, 2025 23:41
-
-
Save RickStrahl/cab310922f5c7b01aa8f6686c7632800 to your computer and use it in GitHub Desktop.
A first-cut NuGet Package Loader
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Paste into LinqPad | |
// dotnet add package NuGet.Protocol | |
async Task Main() | |
{ | |
// package sources | |
string[] sources = ["/projects/Nuget", "https://api.nuget.org/v3/index.json"]; | |
// Package loader: specify output folder for package assemblies | |
var pack = new NuGetPackageLoader(@"d:\temp\Packages"); | |
await pack.LoadPackageAsync("Westwind.Ai", "0.2.4.2", sources); | |
var obj = pack.CreateInstance("Westwind.AI.Configuration.OpenAiConnection"); | |
obj.Dump(); // works | |
} | |
public class NuGetPackageLoader | |
{ | |
private readonly string _packagesFolder; | |
public NuGetPackageLoader(string packagesFolder) | |
{ | |
_packagesFolder = packagesFolder; | |
} | |
public async Task LoadPackageAsync(string packageId, string version, IEnumerable<string> sources = null) | |
{ | |
if (sources == null) | |
sources = ["https://api.nuget.org/v3/index.json"]; | |
var logger = NullLogger.Instance; | |
var cache = new SourceCacheContext(); | |
var repositories = Repository.Provider.GetCoreV3(); | |
var packageVersion = new NuGetVersion(version); | |
var packagePath = Path.Combine(_packagesFolder, packageId, version); | |
Directory.CreateDirectory(packagePath); | |
using var packageStream = new MemoryStream(); | |
FindPackageByIdResource resource = null; | |
foreach (var source in sources) | |
{ | |
if (!source.StartsWith("http")) | |
{ | |
// local packages | |
var sourceRepository = Repository.Factory.GetCoreV3(source); | |
try | |
{ | |
resource = await sourceRepository.GetResourceAsync<FindPackageByIdResource>(); | |
} | |
catch { continue; } | |
} | |
else | |
{ | |
// online packages | |
var packageSource = new PackageSource(source); | |
var sourceRepository = new SourceRepository(packageSource, repositories); | |
try | |
{ | |
resource = await sourceRepository.GetResourceAsync<FindPackageByIdResource>(); | |
} | |
catch { continue; } | |
} | |
if (await resource.CopyNupkgToStreamAsync(packageId, packageVersion, packageStream, cache, logger, default)) | |
{ | |
packageStream.Seek(0, SeekOrigin.Begin); | |
break; | |
} | |
resource = null; | |
} | |
if (resource == null) | |
return; | |
var packageReader = new PackageArchiveReader(packageStream); | |
// find the highest compatible framework | |
string framework = GetTargetFramework(packageReader); | |
// load dependencies | |
var dependencies = packageReader | |
.GetPackageDependencies() | |
.Where(d => d.TargetFramework.ToString() == framework) | |
.ToList(); | |
foreach (var packRef in dependencies) | |
{ | |
var pkg = packRef.Packages.FirstOrDefault(); | |
if (pkg?.Id == null) | |
continue; | |
await LoadPackageAsync(pkg.Id, pkg.VersionRange.OriginalString); | |
} | |
var files = packageReader.GetFiles().Where(f => f.Contains("/" + framework + "/")); | |
foreach (var file in files) | |
{ | |
var filePath = Path.Combine(packagePath, file.Replace("/", "\\")); | |
if (!File.Exists(filePath)) | |
{ | |
Directory.CreateDirectory(Path.GetDirectoryName(filePath)); | |
//FileStream fileStream = null; | |
try | |
{ | |
using var fileStream = File.Create(filePath); | |
await packageReader.GetStream(file).CopyToAsync(fileStream); | |
} | |
catch { /* ignore - most likely the file exists already */ } | |
} | |
if (filePath.EndsWith(".dll") && File.Exists(filePath)) | |
{ | |
var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(filePath); | |
Console.WriteLine($"Loaded assembly: {assembly.FullName}"); | |
} | |
} | |
} | |
// This is non-generic in that it checks for specific framework versions | |
private string GetTargetFramework(PackageArchiveReader packageReader) | |
{ | |
string framework = packageReader.GetReferenceItems(). | |
Where(g => | |
{ | |
string framework = g.TargetFramework.ToString(); | |
if (framework == "net9.0" || framework == "net8.0" || | |
framework == "net6.0" || framework == "net7.0" || | |
framework.StartsWith("netstandard")) | |
return true; | |
return false; | |
}) | |
.OrderByDescending(g => g.TargetFramework.ToString()) | |
.Select(g => g.TargetFramework.ToString()) | |
.FirstOrDefault(); | |
return framework; | |
} | |
#region Object Instantiation | |
public object CreateInstance(string typename) | |
{ | |
try | |
{ | |
Type type = GetTypeFromName(typename); | |
if (type != null) | |
{ | |
object instance = Activator.CreateInstance(type); | |
return instance; | |
} | |
} | |
catch { } | |
return null; | |
} | |
private Type GetTypeFromName(string typeName) | |
{ | |
var type = Type.GetType(typeName, false); | |
if (type != null) | |
return type; | |
var assemblies = AppDomain.CurrentDomain.GetAssemblies(); | |
// try to find manually | |
foreach (Assembly asm in assemblies) | |
{ | |
type = asm.GetType(typeName, false); | |
if (type != null) | |
break; | |
} | |
if (type != null) | |
return type; | |
return null; | |
} | |
#endregion | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment