If you've ever tried to share code like an API proxy, math calculations, or validation logic between multiple projects, you'll know there are many options. In this post, I'll summarize the most common, then dig into the most versatile for .NET projects: private NuGet feeds. I'll cover how to:
- Create private NuGet feeds in Azure DevOps
- Package .NET Core libraries into NuGet packages
- Publish NuGet packages to feeds via Cake (C# Make) including authentication
- Consume NuGet packages from private NuGet feeds
I'll save some of the more interesting bits like automatic semantic versioning, and publishing to private NuGet feeds from an Azure DevOps Build, for a subsequent post.
Bro, Just Ctrl-C, Ctrl-V
Private NuGet feeds are pretty great for many code sharing problems, but they're a little tricky to set up. Before jumping to the Cadillac solution, remember different problems require different approaches.
For instance, I love copy and pasting for simple code that's unlikely to change. It's fast and easy of course, but over time, the code can be customized on each project without any fear of accidentally breaking another project. Obviously, when the code requires a bug fix, update or new feature, then this approach falls apart fast.
Git SubModules seem nice at first because they're easy to set up, but if you've ever used them much, you'll find they're kind of a pain to work with. Also, they don't support versioning, particularly semantic versioning. That means it's hard to know which version of shared code a particular client is running and thus what features or bug fixes it'll get by upgrading.
Private NuGet Feeds are trickier to set up, but they offer an easy and powerful distribution model that clients are already familiar with, a sophisticated security model, and they offer semantic versioning so clients can know exactly what they've got and what they're getting.
Create a Feed
There are a variety of options for private NuGet feeds. You could use MyGet, host your own feed, or use Azure DevOps. If you're already using Azure DevOps for source control or continuous integration, then using it for NuGet feed keeps everything in one place, and makes authentication easy. I'll show the Azure DevOps option in this post, just be aware the free tier limits NuGet feeds to 5 users.
To get started, click on "Artifacts" in the left-menu.
Then New Feed:
Give it a name and permissions. Click Create. Then the "Connect to Feed" panel will provide all the details you'll need to push artifacts.
Packing NuGet
At this point, you could follow the instructions and package and publish to the feed at the command line with a nuget.exe command. However, a one-off command isn't repeatable, and can't be consumed by a continuous integration server.
If you want the option of automating the building, packaging, and publishing of your library, then some kind of scripting is in order. As my regular readers know, I'm a big fan of Cake (C# Make) for devops solutions. It:
- manages dependencies between tasks
- allows running or debugging locally
- is cross platform
- offers intellisense (in VS Code with the plugin)
- is source controlled
- supports migrating between any CI Servers
To use it, you'll need to first compile the library you plan to publish. If you've watched my introduction to Cake video, then you'll be comfortable with the following script to clean and build your library:
Task("Clean")
.Does(() =>
{
CleanDirectory(buildOutputDirectory);
});
Task("Build")
.IsDependentOn("Clean")
.Does(() =>
{
DotNetCoreBuild(projectDirectory, new DotNetCoreBuildSettings {
Configuration = configuration
});
});
If that looks foreign, it's actually just a C# DSL with lambdas that declares two tasks, Clean
and Build
. Build
is dependent on Clean
, so if you run Build
, it will always Clean
first. Seems simple, but dependency management helps prevent devops spaghetti.
What we need now is to package our library up into a nuget package. Fortunately, ASP.NET Core makes this really simple with the "dotnet pack" command, and Cake exposes it with the DotNetCorePack command like this:
Task("Pack")
.IsDependentOn("Build")
.Does(() =>
{
DotNetCorePack(projectDirectory, new DotNetCorePackSettings {
NoBuild = true,
IncludeSymbols = true,
Configuration = configuration
});
});
The NoBuild = true
attribute is important because it keeps us from accidentally building multiple times. Instead, we let cake handle our dependencies. We only ever want the "Build
" task to perform builds.
Running that task (e.g. .\build.ps1 -t Pack on Windows) should result in something like MyLib.1.0.0.nupkg being output to your bin\Release folder. I'll cover how to update the version in the next post in the series.
By the way, instead of packaging explicitly from the command line, another option is to check "Generate NuGet package on build
" in your .csproj file for the Release configuration. Personally, I prefer to do it in cake so all my devops logic in one place, but either option is fine.
Pushing Artifacts to Your Feed
If you follow the instructions in "Connect to Feed", you'd next need to do something like:
nuget.exe push -Source "MyNewFeed" -ApiKey AzureDevOps my_package.nupkg
Switching that over to cake looks like this:
const string nugetFeedUrl =
"https://sirenofshame.pkgs.visualstudio.com/_packaging/CodeSharingFeed/nuget/v3/index.json";
Task("NuGetPush")
.IsDependentOn("Pack")
.Does(() =>
{
NuGetPush(nupkgFile, new NuGetPushSettings {
Source = nugetFeedUrl,
ApiKey = "AzureDevOps",
});
});
Easy enough. Right up until you run it and get this error:
Unable to load the service index for source https://sirenofshame.pkgs.visualstudio.com/_packaging/CodeSharingFeed/nuget/v3/index.json.
Response status code does not indicate success: 401 (Unauthorized).
Authenticating Your Feed
The solution to authenticating is to create a Personal Access Token with Read and Write permissions for Packaging:
Then in Cake, ask for the username and password from the personal access token as parameters, add a new task to create a NuGet Source with those credentials, and set the new task as a dependency for NuGetPush:
var nugetUsername = Argument<string>("nugetUsername", null);
var nugetPassword = Argument<string>("nugetPassword", null);
Task("AddNugetSource")
.Does(() =>
{
if (!NuGetHasSource(nugetFeedUrl)) {
NuGetAddSource("CodeSharingFeed", nugetFeedUrl, new NuGetSourcesSettings {
UserName = nugetUsername,
Password = nugetPassword
});
}
});
Task("NuGetPush")
.IsDependentOn("AddNugetSource")
.IsDependentOn("Pack")
.Does(() => { ... }
Now if you run that puppy like:
.\build.ps1 -target=NuGetPush -nugetUsername=ShareCodeNuGetPackager -nugetPassword=MyPassword
It should succeed and your feed page should look something like this:
Pull Feed
Now that you've published the feed, you're ready to consume it. You can do that in Visual Studio with:
- Tools -> Options
- Nuget Package Manager -> Package Sources
- +
- Add a Name and a Source
Visual Studio will automatically handle Azure DevOps authentication using your signed in credentials. Pretty nice.
But, how does consuming that feed on a CI server work? Not very well at first. I'll cover how to overcome that hurdle in my next post.
Conclusion
In this article, I described the benefits of sharing code with private NuGet feeds, explained how to create feeds, showed how to build, package, authenticate, and push to feeds, and briefly covered how to consume them.
In the next article, I'll cover versioning, building and publishing the library on the build server, and consuming the nuget feed from a Continuous Integration server.
I hope this was helpful. If so, please let me know in the comments or on twitter.
CodeProject