Tuesday 21 May 2013

Retrieving NuGet package programatically using NuGet.exe classes (not command line)

Following from Consuming NuGet programmatically outside VisualStudio (downloading the list of packages) , here is how I was able to consume NuGet.exe directly and create a strongly-typed NuGet Packages object

I started by opening up NuGet.exe in ILSpy and see what it’s Main method is doing:

image_thumb[7]

It looks like there is a Program class that is used to load data and eventually execute a command.

So lets open up a C# REPL script and try this out.

We start with importing the NuGet.exe and creating a new instance of the Program class

image_thumb[10]

Then (following the rewrite of the code in the NuGet’s Main method), create a Nuget.Common.Console object and add a Console Out viewer to the the top panel:

image_thumb[11]

Now after executing the Initialize method (which is private so we need to use reflection to invoke it), we have a list of available commands

image_thumb[12]

which are the same ones that are available via the command line:

image_thumb[13]

If we open up the .details() view, we can see that the info we want is in the CommandAttribute.Description Property

image_thumb[14]

Here is how to access the CommandAttribute property

image_thumb[15]

Here is the description of the first value:

image_thumb[18]

And here is simple foreach loop that collects the descriptions for all commands:

image_thumb[19]

Here is how to create a command’s dictionary indexed by the command’s name:

image_thumb[20]

With this dictionary we can access the command by name:

image_thumb[21]

At this stage, executing the help command throws an error:

image_thumb[23]

But the list command is ok.

image_thumb[24]

we’re getting close now..... :)

Handling SSL Error

If you want to see the traffic that is happening when a list command is executed, and set-up a proxy like Fiddler, you will get this error:

image_thumb[25]

which can be resolved using the Web.Https.ignoreServerSslErrors FluentSharp method

image_thumb[26]

Now in fiddler we can see that there are a bunch of requests made, in this format:

GET /api/v2/Search()?$filter=IsLatestVersion&$orderby=Id&$skip=0&$top=30&searchTerm=''&targetFramework=''&includePrerelease=false
GET /api/v2/Search()?$filter=IsLatestVersion&$orderby=Id&$skip=30&$top=30&searchTerm=''&targetFramework=''&includePrerelease=false
….
GET /api/v2/Search()?$filter=IsLatestVersion&$orderby=Id&$skip=270&$top=30&searchTerm=''&targetFramework=''&includePrerelease=false
….
GET /api/v2/Search()?$filter=IsLatestVersion&$orderby=Id&$skip=600&$top=30&searchTerm=''&targetFramework=''&includePrerelease=false


image_thumb[28]

After a bit of experimentation it looks like 30ish is the max that can be retrieved by that GET command.

Also there is a LOT of stuff in there (specially when all I wanted was the list of available package names)

image_thumb[29]

Btw, where is the NuGet v2 API spec, I can’t see to find it and http://nuget.codeplex.com/wikipage?title=Specs says:

image_thumb[30]

Retrieving the packages data:

So looking at the the list command code, we can see that it’s GetPackages() method is the one used to control the download (the rest of this method displays the data in the console out):

image_thumb[32]

The .GetPackages method is an IEnumerable which means that it will trigger the requests on demand

image_thumb[33]

Now ideally, since we can create an instance of the ListCommand class

image_thumb[34]

We should be able to just execute it’s GetPackages method (sepecially since when the normal ExecuteCommand method stores the GetPackages in a local variable (to the ExecuteCommand method)

image_thumb[35]

But that doesn’t work because we get this error:

image_thumb[37]

Which basically means that there is some object that needs to be set before the GetPackages call is made

And that is on the Command object Execute method (which is the one executed when we call listCommand.Execute)

image_thumb[38]

with the ExecuteCommand being the abstract method (overriden by the actual command class (like the ListCommand))

image_thumb[39]

Ok, so this is a bit of a pickle since clearly there are a bunch of objects that are set in the Command.Execute which need to be set before we can call the ListCommand.GetPackages method.

BUT, the Command.Execute will call the ListCommand.ExecuteCommand, which will trigger the (non desired behaviour of) downloading of all available packages.

The solution is to (re)implement this method in our script using reflection.

This would be easier if all the classes and properties used where public, but since they are not, we need to use a bit of reflection.

Here is the config part of the Command.Execute method (with this.ConfigFile== null  and minus the last line):

image_thumb[42]

(in comments are the original lines of code, followed by the C# script that does the same thing using reflection and inside a lambda method)

And now it works :)

image_thumb[43]

We can control how many articles are fetched by using the .take(…) command:

image_thumb[44]

And if we look at the traffic that happens, when we request 29 items:

image_thumb[46]

there will be one request made

image_thumb[45]

.... if we ask for 50:

image_thumb[48]

there will be 2 requests made

image_thumb[49]

... and for 250:

image_thumb[51]

there will be 9 requests:

image_thumb[50]

And if we ask for all packages:

image_thumb[52]

we will get 406 requests (and +/- 12180 items)

image_thumb[31]

At this stage we have found a way programmatically access the NuGet Commands included with NuGet.exe, to for example get the entire list of NuGet Packages as nice strongly-typed objects (note that these objects all the info we could ever want from NuGet)


See also Offline copy of the entire NuGet.org gallery. What should I do with these 4.05 Gbs of amazing .Net Apps/APIs?