如何以编程方式安装NuGet包?

5

我希望能以编程的方式将NuGet包安装到项目中,并更新.csproj文件和packages.config文件。

我正在使用官方的Nuget.core框架,其源代码可在此处找到:https://github.com/NuGet/NuGet2

我没有使用NuGet包:https://www.nuget.org/packages/NuGet.Core/,而是在GitHub上找到了源代码以进行一些调试。

注意:我使用的版本是2.11而不是2.13

我能够在所需目录下载包并更新packages.config文件:

// ---- Download and install a package at a desired path ----
string packageID = "Newtonsoft.json";
var sourceUri = new Uri("https://packages.nuget.org/api/v2");

// Return an IPackage
var package = GetNugetPackage(packageID, sourceUri);

IPackageRepository sourceRepository = PackageRepositoryFactory.Default.CreateRepository(sourceUri.ToString());

string packagesPath = "../../TestFiles/packages";
PackageManager packageManager = new PackageManager(sourceRepository, packagesPath);

packageManager.InstallPackage(packageID, SemanticVersion.Parse(package.Version.ToFullString()));

// ---- Update the ‘packages.config’ file ----
var packageReferenceFile = new PackageReferenceFile("../../TestFiles/packages.config");

// Get the target framework of the current project to add --> targetframework="net452" attribute in the package.config file
var currentTargetFw = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(TargetFrameworkAttribute), false);
var targetFrameworkAttribute = ((TargetFrameworkAttribute[])currentTargetFw).FirstOrDefault();

// Update the packages.config file    
packageReferenceFile.AddEntry(package.GetFullName(), SemanticVersion.Parse(package.Version.ToFullString()), false, new FrameworkName(targetFrameworkAttribute.FrameworkName));

现在我需要更新.csproj文件,这是比较棘手的部分...
到目前为止,我尝试了以下方法:
string csprojFilePath = "../../TestFiles/test.csproj";
var project = new MSBuildProjectSystem(csprojFilePath);

string pathToAnExistingNugetPackageDll = "../../TestFiles/packages/Newtonsoft.json/lib/net45/Newtonsoft.json.dll"

project.AddReference(pathToAnExistingNugetPackageDll, Stream.Null);
project.Save();

这段代码更新了.csproj文件,它添加了一个新的reference节点,就像这样:

<Reference Include="Newtonsoft.json">
  <HintPath>..\packages\Newtonsoft.json\lib\net45\Newtonsoft.json.dll</HintPath>      
</Reference>

但我需要像这样的完整的参考节点:

<Reference Include="Newtonsoft.json, Version=9.0.8, Culture=neutral, PublicKeyToken=b03f4f7d11d50a3a, processorArchitecture=MSIL">
  <HintPath>..\packages\Newtonsoft.json\lib\net45\Newtonsoft.json.dll</HintPath>
  <Private>True</Private>
</Reference>

我该如何做呢?

1
你需要使用自己的代码生成引用。那种特定风格的引用是由Visual Studio生成的,而不是NuGet。NuGet使用不使用完整程序集名称的Include属性生成引用。 - Matt Ward
我找到了一个看起来可以在核心程序集中完成任务的类:'RemoteAssembly',它似乎可以加载我所需的属性... 我会试一试。 - Thomas
@Thomas,找不到MSBuildProjectSystem。你能给出它的程序集引用吗? - MB_18
2个回答

11

感谢@MattWard@bashis,我已经编写了自己的代码生成器:

因此,要将引用正确添加到给定的.csproj中,这是我所做的:

var CsprojDoc = new XmlDocument();
CsprojDoc.LoadXml("*your_csproj_content*");
var Nsmgr = new XmlNamespaceManager(CsprojDoc.NameTable);
Nsmgr.AddNamespace("x", "http://schemas.microsoft.com/developer/msbuild/2003");

IPackage packageInfos = GetNugetPackage(packageId, packageRepositoryUri);

XmlNode referenceNode = CsprojDoc.CreateNode(XmlNodeType.Element, "Reference", XmlNamespaceValue);
XmlAttribute includeAttribute = CsprojDoc.CreateAttribute("Include");

var targetFwProfile = CsprojDoc.SelectSingleNode("//x:TargetFrameworkProfile", Nsmgr);
string targetFrameworkProfile = string.Empty;
if (!string.IsNullOrEmpty(targetFwProfile?.InnerXml))
{
    targetFrameworkProfile = targetFwProfile.InnerXml;
}

var targetFwAttribute = GetTargetFrameworkFromCsproj();
Regex p = new Regex(@"\d+(\.\d+)+");
Match m = p.Match(targetFwAttribute.FrameworkName);
Version targetFwVersion = Version.Parse(m.Value);

// Get the package's assembly reference matching the target framework from the given '.csproj'.
var assemblyReference =
    packageInfos.AssemblyReferences
        .Where(a => a.TargetFramework.Identifier.Equals(targetFwAttribute.FrameworkName.Split(',').First()))
        .Where(a => a.TargetFramework.Profile.Equals(targetFrameworkProfile))
        .Last(a => (a.TargetFramework.Version.Major.Equals(targetFwVersion.Major) && a.TargetFramework.Version.Minor.Equals(targetFwVersion.Minor)) ||
        a.TargetFramework.Version.Major.Equals(targetFwVersion.Major));

DownloadNugetPackage(packageInfos.Id, packageRepositoryUri, packagesFolderPath, packageInfos.Version.ToFullString());

string dllAbsolutePath = Path.GetFullPath($"{packagesFolderPath}\\{packageInfos.GetFullName().Replace(' ', '.')}\\{assemblyReference.Path}");
var assemblyInfos = Assembly.LoadFile(dllAbsolutePath);

includeAttribute.Value = $"{assemblyInfos.FullName}, processorArchitecture=MSIL";

referenceNode.Attributes.Append(includeAttribute);

XmlNode hintPathNode = CsprojDoc.CreateNode(XmlNodeType.Element, "HintPath", XmlNamespaceValue);
XmlNode privateNode = CsprojDoc.CreateNode(XmlNodeType.Element, "Private", XmlNamespaceValue);

hintPathNode.InnerXml = $"$(SolutionDir)\\packages\\{assemblyReference.Path}";
privateNode.InnerXml = "True";

referenceNode.AppendChild(hintPathNode);
referenceNode.AppendChild(privateNode);
var itemGroupNode = CsprojDoc.SelectSingleNode("//x:Project/x:ItemGroup/x:Reference", Nsmgr).ParentNode;
itemGroupNode.AppendChild(referenceNode);

这是我的DownloadNugetPackage方法:

private static void DownloadNugetPackage(string packageId, Uri repoUri, string packagesFolderPath, string version)
{
    IPackageRepository packageRepository = PackageRepositoryFactory.Default.CreateRepository(repoUri.ToString());
    PackageManager packageManager = new PackageManager(packageRepository, packagesFolderPath);

    packageManager.InstallPackage(packageId, SemanticVersion.Parse(version));
}

我的GetTargetFrameworkFromCsproj

    public static TargetFrameworkAttribute GetTargetFrameworkFromCsproj()
    {
        XmlNode targetFrameworkNode = CsprojDoc.SelectSingleNode("//x:TargetFrameworkVersion", Nsmgr);
        return new TargetFrameworkAttribute($".NETFramework, Version={targetFrameworkNode.InnerXml}");
    }

我的GetNugetPackage方法:

public static IPackage GetNugetPackage(string packageId, Uri repoUri, string version = null)
{
    IPackageRepository packageRepository = PackageRepositoryFactory.Default.CreateRepository(repoUri.ToString());
    IPackage package;

    if (!string.IsNullOrEmpty(version))
    {
        package = packageRepository.FindPackagesById(packageId).SingleOrDefault(p => p.Version.ToFullString().Equals(version));
    }
    else
    {
        package = packageRepository.FindPackagesById(packageId).SingleOrDefault(p => p.IsLatestVersion);
    }

    return package;
}

注意:这次我使用的是官方NuGet包:Nuget.core 2.14https://www.nuget.org/packages/NuGet.Core/

注意2:当我添加一个新的Reference节点时,在processorArchitecture属性中,我已经硬编码了值:MSIL

如果您想测试它,您可以进行一些微调。

这对我来说很好用。


感谢您回来并提供答案,我自己不需要,但这是一个罕见(但非常受欢迎)的景象 :-) - James Gould

1
(假设你正在自己编写代码生成器)
我不确定这是最好的解决方案,但你可以尝试类似以下方法:
var assembly = Assembly.LoadFile("path_to_dll");
string info = assembly.FullName; // contains something like "AssemblyName, Version=0.0.0.0, Culture=neutral, PublicKeyToken=foobar"

这将通过加载程序集添加一些开销,但很可能会完成任务。


这会帮助我很多,如果我没能解决怎样与nuget.core配合使用的问题,我将使用你的方法创建自己的生成器。 - Thomas
我正在编写自己的代码生成器,你的代码很好用,谢谢。 - Thomas

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