Nuget Packaging Series: Introduction

Recently, I was tasked with building a nuget package for a plugin project with the following properties

  1. The plugin assembly has a small public API implemented through multiple projects
  2. Output assemblies from referenced projects needed to be included in the same package
  3. Different versions of a third party managed assembly needed to be included for dynamic loading at runtime
  4. Output assemblies from non-referenced projects needed to be included in the same package
  5. The consumer should not have to know anything about the package structure or 2. even existing
  6. Developers should notice package build errors as quickly as possible

Solving these requirements was surprisingly frustrating for several reasons. First was probably my (now partially lessened) ignorance regarding how MSBuild and nuget packages work. Second only to that was that requirement 2 apparently already goes against fundamental design decisions of nuget, and has been causing lengthy discussions about possible workarounds. And third, dotnet pack seemed to have been a moving target for quite a while with documentation and blog posts becoming outdated quickly.

In the end, thanks to this task, I now have a much improved understanding of what MSBuild and nuget actually do.

Setting the scene

The project structure looked like this:

And the csproj of the plugin project looked like this:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <PackageId>MyLibrary</PackageId>
    <Version>1.0.0</Version>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\MyLibrary.Supporting1\MyLibrary.Supporting1.csproj" />
    <ProjectReference Include="..\MyLibrary.Supporting2\MyLibrary.Supporting2.csproj" />
  </ItemGroup>

</Project>

I use netstandard 2.0 for demo purposes here. In real life all projects were .NET Framework 4.8 with SDK-style csproj. This makes using stand-alone nuget rather awkward. In addition, I specifically did not want to build the package manually with a hand-written nuspec file. With no automated build process or git pre-commit hooks established at the time, nuspec files would become outdated without anyone noticing until the next manual package build. I wanted myself and other developers to immediately know during development when something was wrong with the package build. The solution needed to work with dotnet pack alone.

Although solutions and workarounds for above requirements are aplenty, I myself as both an MSBuild and nuget noob thought that a lot of them lack a comprehensive explanation. The descriptions skip steps, mix concerns in their solutions or provide little context about applying the techniques outside of the example projects. I will try to describe in more detail what is going on and provide context that could be useful for other real-life applications.

While I tried out different configurations, I was more than once fooled by seemingly successful package builds that did not actually update the package file. If you follow along these instructions or play around with the complete demo from my github, you may want to:

  • use dotnet pack instead of Visual Studio to make sure VS’s build cache is not playing tricks on you
  • not have the package file open in dotpeek, Nuget Package Explorer or similar tools while building

Let’s go solve the first requirements in the first actual post of the series.