In this article I want to write a few words about the legendary NuGet package manager. First of all I need to clarify what will not be discussed in this article:
- What NuGet is exactly
- How we can install NuGet
- How to get packages over NuGet
- How to update packages over NuGet
Maybe this is a good point to actually tell write what we will do in the next paragraphs:
- Assemble our own NuGet package
- Compile our own NuGet package
- Distribute our own NuGet package over the internet
- Update our NuGet package
- Create our own network service with NuGet packages
- Use our own network service to distribute, update and install packages
The process of creating packages is fairly easy (which is of course part of the success of NuGet). Now that we have installed (specifically: the latest version) NuGet we can power up the Windows powershell. This is the easiest way to access NuGet outside of the Visual Studio environment. There are some GUI helpers, but writing a few commands has the advantage that we already know how to script / automate any relevant NuGet process later on.
NuGet command line tool should already be wired up with all necessary path variables. This means that we can just type in the following:
This will automatically search for the latest NuGet version and install it (if it is newer than the current version). Now we need to understand the following three steps for creating a NuGet package:
- Organize the package in folders (this is convention based)
- Specify the metadata (like author, version, ...) in a special file
- Pack everything together into one special file
The first step follows some conventions that we will discuss shortly. The second one can be done quickly, if we generate a template for the special file by using NuGet. The last part is also done by the NuGet program, leaving us behind with not really that much work.
A NuGet package is based on the following folder structure:
The root directory (denoted by *) contains nuspec file (metadata). We will talk about the metadata file in the next section. For now we just need to know that the location of this file determines the root directory. The other directories from the list above are then assumed. These assumptions are being set by convention, i.e. we could alter them. However, it would be wise to follow the conventions, since breaking conventions can make things more complicated.
Let's have a look at the roles of each folder:
- lib Here we store the assemblies (e.g. dll files) that will get referenced in the target project
- content Files within this folder are copied to the application root before installing the package
- tools This folder contains powershell scripts that may run during the installation or initialization of the solution
Usually the lib folder is containing some sub folders. This is required if we want to specify some .NET-Framework dependencies. An example would be:
The files in the folder ./lib/net11 will target the .NET-Framework 1.1, while the files in the net40 folder will target the .NET-Framework 4.0. The sl4 folder is used for Silverlight 4.0 files. The sl4-wp folder is used to target Silverlight 4.0 in the Windows Phone profile (here wp is the Windows Phone profile). Two other profile types are Client and Full. As an example we also show the dependency of a .NET 3.5 Client profile (folder net35-client). If we place files directly in the lib folder we do not specify any associated framework, i.e. the fill will be referenced on any platform.
The package should contain some information like the author(s), a logo, a description as well as version number and possible dependencies. All those informations are brought together in a *.nuspec file. This is a plain XML file that can be read by the NuGet program. We could either copy and paste an existing one or let NuGet create a wonderful template, based on the (core) assembly that we want to provide with our package. The following command snippet will try to create a good *.nuspec file for us:
nuget spec -AssemblyPath PATH-TO-DLL
Of course NuGet can only guess some values or leave them blank. After all, NuGet did only use the metadata stored in the assembly to create a boilerplate of a *.nuspec file. Therefore editing this file is always necessary. All nodes below the
<metadata> node can (and maybe should) be edited without worrying about breaking NuGet. Let's review the tags:
- <id> (Required) STRING
- <version> (Required) VERSION, up to four segments
- <title> STRING
- <authors> (Required) STRING, comma-separated
- <owners> STRING, comma-separated
- <licenseUrl>, URL
- <projectUrl>, URL
- <iconUrl>, URL, 32x32 .png file
- <requireLicenseAcceptance>, BOOL, true or false
- <description> (Required)
- <tags>, STRING, space-separated
- <dependencies>, LIST of <dependency> elements
- <language>, STRING, LCID such as en-us
- <summary>, STRING
Most items of this list are pretty obvious. It should be noted that id can be (in some cases) the same as the title. If no title is specified, the id will be taken as title. Otherwise the id should be URL friendly and short (without spaces). The id will be used on the NuGet website as well as for updating the package.
Let's have a closer look at the <dependency> element. Here we have self-closing elements like the following:
<dependency id="FirstSampleDependency" version="[1.0]" />
<dependency id="SomeSampleDependency" version="1.3.2" />
<dependency id="AnotherSampleDependency" version="[1.0, 2.0)" />
<dependency id="LastSampleDependency" version="(, 1.0)" />
Here we have 4 dependencies specified. The first one required exactly version 1.0 of the package with ID FirstSampleDependency. The second one requires version 1.3.2 or higher of the package with ID SomeSampleDependency. For AnotherSampleDependency we specified a version range between version 1.0 (included) and version 2.0 (excluded). The last dependency requires the package LastSampleDependency with a version less than 1.0.
It is worth noting that a dependency must exist as a NuGet package, before we can use it in our list of dependencies. If a library / package is not available over NuGet, but required as a dependency in our list, we need to contact the owner of the library. In such a case we would politely request him to distribute his work as a NuGet package.
Packing everything together
A NuGet package is nothing more than a zip file, containing the specifications file and our specified folder structure. Additionally other files can be included as well. Such files can be specified in the *.nuspec file and are not part of this article. Packing everything together is done by the
NuGet program. If our nuspec file is called mypackage.nuspec we will call the following command:
nuget pack mypackage.nuspec
Now we will have a new file in the root directory (*). The file will usually be named id.version.nupkg. If our ID was also set to mypackage with version 18.104.22.168 we have the following directory listing:
For publishing a package we need to bind the NuGet program to our current API key. An API key can be obtained by using the NuGet webpage. Of course we need a proper account on the webpage before we can obtain an API key. Once we got the API key we can then set it by using:
nuget setapikey YOUR-API-KEY
Now we can publish this package by typing in the following command:
nuget push mypackage.22.214.171.124.nupkg
This command basically publishes our package on the public NuGet feed.
The updating process (usually) involves a few minor steps:
- Updating the assembly
- Probably adding / removing or updating some of the content files / tools
- Updating the version number in the specification file
- Probably updating dependencies or other parts of the specification
- Packing it (again) using the same command as above:
nuget pack mypackage.nuspec
- Publishing it again - this time we have to use the new / updated package:
nuget push mypackage.126.96.36.199.nupkg
NuGet then automatically shows the latest version in the official feed, as well as on the website. Those steps are easily scriptable, i.e. it could run as a post-built event.
There are three really important things for performing a NuGet package update:
- Updating the assembly
- Updating the version number in the *.nuspec file
- Creating a new package
Uploading the old package (no new package has been created) as well as overwriting the old package (version number was not changed) or forgetting to deliver the update itself are errors, that should not happen when providing a package update.
Hosting a private NuGet feed
There are packages which are either confidential or only useable in a limited environment (e.g. the network of a company). Uploading packages to the public NuGet feed seems either unnecessary or a security threat in such cases. However, using NuGet / a package manager can make sense. Let's think about it for a second:
- A package manager allows you to stay updated while others bring up newer versions
- A package manager simplifies dependencies - you do not need to reference 10 libraries to forget #11, you just install the most important one and all other will follow
- A package manager allows you to find possible solutions - maybe someone did already write a library for it, but you just don't know: if the package has been published you will find it
Those reasons alone should be sufficient to use a proper package manager. One important remark here: A package manager is not (a replacement for) a version control system! VCS like Team Foundation Server, Git, Mercurial, SVN or Visual SourceSafe are also important and should be used. However, usually libraries are developed independently of other projects (where those libraries are then finally used), resulting in the need of a package manager.
Now we come to the part where we create our own feed. All in all a NuGet server can be created easily by using the package NuGet.Server. So one easy way of getting a nice (web-)server is by creating a new empty Web Application with the help of Visual Studio 2010 and installing the package.
The default mode is to already serve the packages that have been placed in the Packages subdirectory of the application. After starting the server (that means either the local, just for debbuging purposes IIS like called Cassini - or the real IIS) you can include the path to this application in your Visual Studio. The feeds are served, with the webpage still looking pretty ugly, however, the HTML version of webpage is not needed for Visual Studio integration and downloading packages.
In this standard version the feed is read-only, i.e. we cannot write to the feed by using the
nuget command. The only way to add packages is to upload them directly to the Packages subdirectory of the application.
There is also another option to host a private feed (which does not require a running webserver - a NAS or something similar is sufficient): We could just enter the path to a directory, where all *.nupkg files for our private feed are available. This option requires less work and maybe less resources (if we consider a running IIS to be a resource). However, uploading *.nupkg files is only possible by copy operations with the sufficient rights (so in this case the users need access to the storage device and proper permissions for the corresponding directory).
Using the private NuGet feed
In this section we will have a look at possible extensions to the private NuGet feed. We will have a look at 2 questions:
- How do we upload packages as with the public feed?
- How can we change the look of the webpage?
At this point we have two options:
- Get a paid MyGet account with some private feeds
- Extend the basic web application we just looked at
While option one offers some clear benefits (we do not need to private a server, we do not need to write and test code, ...) it has one significant drawback: our feeds / data would lie on a different server than one within the boundaries of our company network. This raises security issues / confidential problems. Since we wanted to avoid this (and since the first option is really trivial), we will go for the second option and alter the code.
The second option gives us again two possibilities. The first one is the to alter the code that has been mentioned. Here we can be creative in designing an upload form or even provide the possibility to push packages to the feed directly (the required version for the NuGet.Server package is 1.5 or higher). In order to do this we just need to set an API key in the app settings section of the web.config file. An example:
<add key="apiKey" value="pssst" />
If we leave the
apiKey value empty, we are in the write-protected mode as mentioned before. If we specify a key (usually a strong password), then users are able to push packages to our feed by writing the following command:
nuget push PACKAGE-FILE -s SERVER-URL/API-KEY
So here we are using the regular
push argument in combination with the path to our package (this is the same command as before). Specifying the
-s flag we can enter the url to our server. Since we specified a password (the
apiKey value in the
appSettings section), we need to pass this to the server as well. This is done by using it in a URL like statement. An example for our server being located on the server with the IP 10.0.5.91 with the key specified above:
nuget push mypackage.188.8.131.52.nupkg -s http://10.0.5.91/pssst
The second option is to clone and use the repository of the original NuGet web application. The code can be found at codeplex and uses the Mercurial VCS. Here we need to do a little bit more (changing some variables, setting up a database, ...), but in the end we will do the following as above, just without the
apiKey, since we now have to have a proper API key for each user (the web application uses the same construct as the original NuGet website, i.e. each registred user has one API key that can be used to deploy NuGet packages).
The following way helps us focusing on our private nuget feed:
nuget sources Add -Name "My Private Feed" -Source "http://10.0.5.91/"
If we remove the original (public) feed, by performing the following command, we will automatically deploy to the private feed by default.
nuget sources Remove -Name "NuGet official package source"
That's it! Our private NuGet feed is ready to use and can be used the same way as the public feed (Register on the page, get and set your API key, pack and push to the feed).