5

I want to programmatically install a NuGet package to a project, and update the .csproj file, and the packages.config file.

I am using the official Nuget.core framework which source code is available here: https://github.com/NuGet/NuGet2

I am not using the NuGet package: https://www.nuget.org/packages/NuGet.Core/ But the source code found on GitHub to be able to do some debugging.

Note: I am using the version 2.11 and not the 2.13

I am able to download a package at a desired directory and update the packages.config file:

// ---- 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));

Now I need to update the .csproj and here is the tricky part...

Here's what I tried so far:

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();

This piece of code update the .csproj file, it add a new reference node like this:

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

But I need a complete reference node like this:

<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>

How can I do it ?

Manfred Radlwimmer
  • 13,257
  • 13
  • 53
  • 62
Thomas
  • 1,289
  • 1
  • 17
  • 29
  • 1
    You will need to generate the reference yourself with your own code. That particular style of reference is generated by Visual Studio and not by NuGet. NuGet generates the reference with an Include attribute that does not use the full assembly name. – Matt Ward Jan 23 '17 at 10:22
  • I found a class which seem to do the trick in the Core assembly: 'RemoteAssembly' it seem to load my needed attributes... I'll give it a try – Thomas Jan 23 '17 at 10:26
  • @Thomas , `MSBuildProjectSystem` could not be found. Could you give its assembly reference? – MB_18 Oct 10 '22 at 12:57

2 Answers2

11

Thanks to @MattWard and @bashis, i've written my own code generator:

So, to add properly a reference into a given .csproj here's what I do:

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);

Here's my DownloadNugetPackage method:

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));
}

My GetTargetFrameworkFromCsproj

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

And my GetNugetPackage method:

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;
}

Note: This time, i'm using the official NuGet package: Nuget.core 2.14: https://www.nuget.org/packages/NuGet.Core/

Note 2: When I add a new Reference node, in the processorArchitecture attribute, I've hard coded the value: MSIL

If you want to test it, you might tweak it a bit.

This work fine for me.

Community
  • 1
  • 1
Thomas
  • 1,289
  • 1
  • 17
  • 29
  • Thanks for coming back and providing an answer, I don't need this myself but it's a rare (but very welcome) sight :-) – James Gould Jan 27 '17 at 13:54
1

(Assuming you are writing the code generator on your own)

I am not sure this is the best solution but you could try something like this:

var assembly = Assembly.LoadFile("path_to_dll");
string info = assembly.FullName; // contains something like "AssemblyName, Version=0.0.0.0, Culture=neutral, PublicKeyToken=foobar"

which will add some overhead by loading the assembly, but will presumably do the job.

bashis
  • 1,200
  • 1
  • 16
  • 35
  • This will help me a lot, if I don't figured out how to make it work with nuget.core, i'll use your method to create my own generator. – Thomas Jan 23 '17 at 10:40
  • I'm writing my own code generator, and your code do the trick, thanks – Thomas Jan 24 '17 at 11:12