在运行时加载 NuGet 依赖项

28

我正在寻找一种通过执行以下步骤来运行代码的方法:

  1. 接收NuGet包列表(元组列表(“包名称”,“包版本”,“主类路径”))。
  2. 在本地目录中检索它们(参见代码示例#1)
  3. 在运行时加载它们到我的程序中
  4. 通过内省运行主类(参见代码示例#2)

目前,我正在努力完成第三步。我找不到如何在运行时加载我的包的方法。

我的主要问题是:

  • 如何找出检索到的包存储在哪些文件夹中?
  • 如何将这些目录的内容加载到我的程序中?

代码示例#1:

private static void getPackageByNameAndVersion(string packageID, string version)
{
    IPackageRepository repo =
            PackageRepositoryFactory.Default
                  .CreateRepository("https://packages.nuget.org/api/v2");

   string path = "C:/tmp_repo";
   PackageManager packageManager = new PackageManager(repo, path);
   Console.WriteLine("before dl pkg");
   packageManager.InstallPackage(packageID, SemanticVersion.Parse(version));

}

代码示例#2:

private static void loadByAssemblyNameAndTypeName(string assemblyName, string typeName)
{
   AppDomain isolationAppDomain = AppDomain.CreateDomain("tmp");
   object a = isolationAppDomain.CreateInstanceAndUnwrap(assemblyName, typeName);
   Type x = a.GetType();
   MethodInfo m = x.GetMethod("Main");
   m.Invoke(a, new object[] { });
}

看起来OP正在使用NuGet.PackageManagement包。 - Tom Charles Zhang
2个回答

35

来杯咖啡 :)

下载Nuget包?

Nuget.Core(Nuget包)是一个不错的选择,这里有一小段代码,可以通过 idversion 下载Nuget包:

var repo = PackageRepositoryFactory.Default
                .CreateRepository("https://packages.nuget.org/api/v2");

string path = "c:\\temp";
var packageManager = new PackageManager(repo, path);
packageManager.PackageInstalled += PackageManager_PackageInstalled;

var package = repo.FindPackage("packageName", SemanticVersion.Parse("1.0.0"));
if (package != null)
{
    packageManager.InstallPackage(package, false, true);
}

请注意,我将一个事件处理程序插入到PackageManager类的PackageInstalled事件中。

我们如何在隔离的应用程序域中加载程序集?

由于反射API没有提供一种在特定域中加载程序集的方式,因此我们将创建一个代理类,在我们的隔离域中充当加载器:

public class TypeProxy : MarshalByRefObject
{
    public Type LoadFromAssembly(string assemblyPath, string typeName)
    {
        try
        {
            var asm = Assembly.LoadFile(assemblyPath);
            return asm.GetType(typeName);
        }
        catch (Exception) { return null; }
    }
}

现在,如何将所有这些东西组合起来呢?

接下来是复杂的部分:

private static void PackageManager_PackageInstalled(object sender, 
                                                    PackageOperationEventArgs e)
{
    var files = e.FileSystem.GetFiles(e.InstallPath, "*.dll", true);
    foreach (var file in files)
    {
        try
        {
            AppDomain domain = AppDomain.CreateDomain("tmp");
            Type typeProxyType = typeof(TypeProxy);
            var typeProxyInstance = (TypeProxy)domain.CreateInstanceAndUnwrap(
                    typeProxyType.Assembly.FullName,
                    typeProxyType.FullName);

            var type = typeProxyInstance.LoadFromAssembly(file, "<KnownTypeName>");
            object instance = 
                domain.CreateInstanceAndUnwrap(type.Assembly.FullName, type.FullName);
        }
        catch (Exception ex)
        {
            Console.WriteLine("failed to load {0}", file);
            Console.WriteLine(ex.ToString());
        }

    }
}

请注意,这个方法是在下载Nuget包后执行的事件处理程序

此外,

请注意,您需要将<KnownTypeName>替换为来自程序集的预期类型名称(或者可能运行程序集中所有公共类型的发现)


值得注意的是,我自己没有执行过这段代码,不能保证它可以立即工作,可能仍需要一些微调。但希望它给了您解决问题的概念。


3
你的例子有一个问题,就是当被询问的包在 c:\temp 中第一次安装时才会调用 PackageManager_PackageInstalled。如果它已经存在于先前的代码执行中,则不会调用 PackageManager_PackageInstalled。 - Manuel Leduc

-8

不要这样做! 你可能正在尝试在客户计算机上加载NuGet内容,以节省软件分发时的空间。是这样吗?

通常推荐的方法是,在下载源代码后,将NuGet内容作为自动化构建的第二步下载,构建软件并使用您已经下载的NuGet内容运行自动化测试。然后将带有您已测试的NuGet内容的构建作为整个复杂单元进行分发。


7
我了解你的担忧,但我编写软件的主要目标是在运行时加载软件组件。因此,加载的代码将是第三方的,我无法在构建时知道。不管怎样,还是感谢您的明智建议 :) - Manuel Leduc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接