I recently wrote about NuGet packing on linux specifically, and want to continue the theme with other things you can do with NuGet packaging.

As a quick recap you can use a .Net Core *.csproj as a wrapper around a *.nuspec file and use dotnet pack to pack it, cross-platform, without needing nuget.exe.

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

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <NuspecFile>mynewapp.nuspec</NuspecFile>
    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
    <SkipCompilerExecution>true</SkipCompilerExecution>
    <CopyBuildOutputToOutputDirectory>false</CopyBuildOutputToOutputDirectory>
  </PropertyGroup>

</Project>

(See the other article for more detailed analysis of these properties.)

Now for something new.

ContentFiles

Do you remember Content files? There was(is?) this feature in NuGet files where you can include files, and have those files automatically added to your .Net Framework applications when the Package is Installed. Some libraries used this to distribute code that could be embedded into your application instead of linking to a library dependency. Dapper was one example that did this, and I think some logging frameworks also distributed some code this way.

There were a tonne of downsides to this approach. The files were editable, they were put into source control, and they didn't update automatically when you updated the NuGet package. These might sound like upsides, and they were probably best practice given the circumstances. But from a distributer's point of view, these were downsides.

There were other issues as well, especially now we have dotnet core cli, and we have cross-platform builds. This approach is no longer viable in most situations.

Luckily, there is a replacement to Content files, and that is ContentFiles. NuGet ContentFiles Demystified
seems to be the canonical reference post on what these are and how they work, and if you want to read the spec for how to use these with nuspec, that is documented here.

How do we make this work with dotnet pack without using the nuspec trick above? Say you are already packaging a DLL, and want to add an extra file to the mix? Here is the answer:

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

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <Compile Remove="IncludedFile.cs" />
    <Content Include="IncludedFile.cs" BuildAction="Compile" />
  </ItemGroup>

</Project>

Let's say this is an application with a bunch of source files. We have one source file that rather than compile it in, we want to include into the project that uses the package at compile time. First, we remove our file from compiling inside our app, then we make it Content. By default, any Content files will become contentFiles in the NuGet package. It is that easy.

There is one cool trick you can do here, too. if you call your file *.cs.pp, you can run transforms over it. The most (and only) useful one for us is the namespace placeholder. Anywhere in the file you write $rootnamespace$ it will be replaced by the namespace of the project that imports the package, before that library compiles this file. Very handy to ensure that the user's own namespace is used when compiling this file.

A few notes:

  • If the code in that file doesn't compile, consumers will not be able to compile their application (maybe look at using preprocessor directives for different framework targets.
  • Clients need to include contentfiles Assets when they reference the library or they won't get the files, and they can also use that feature to explicitly turn that off. Document this for them. (specifically <IncludeAssets>contentfiles;</IncludeAssets>, but IncludeAssets is set to include all asset types by default anyway.)
  • The contentFiles are included per language, per framework, based on the TargetFramework you are building against (the same way the DLLs do). This means the files will only work with the same language as the project (by default) and the same target frameworks by default. My understanding is that this still follows the usual highest compatible resolution.
  • If you don't want your NuGet to pack content files, you can use the <IncludeContentInPack>false</IncludeContentInPack> setting to turn that feature off completely.
  • you can set Pack="false" onto a Content element to exclude it from packaging as one of the contentFiles. You can also set Pack="true" on None and Compile elements as well for them to be included the same way.
    As well as BuildAction Compile, there are a couple of others: Content, None, Embedded Resource. The same way you can define a file to be one of these in your csproj, the project that includes your contentFiles will have them set to that specified item type. Content might be useful if you want a file to be copied to their build output folder for some reason. I can't seem to think of a valid reason for None, though.

It turns out it is that simple to do contentFiles out of the box.

build props and targets

You can arbitrarily include any file into any location in the NuGet package if you like. There is a special folder called build that contains props and targets to extend MSBuild. For instance, we might want to have a props file

<None Include="MyNewApp.props" PackagePath="build/$(TargetFramework)" Pack="true" />

If you wanted the same file to work for all targets, you could simply just use PackagePath="build".

When using props and targets, you want your entry file to be the same name as the project/package, so that it will get loaded by the project that consumes your NuGet package.

More about how the nuspec file is supposed to look for props and targets can be found here.

Note they recommend using a min client version to let clients know you are using this feature:

<PropertyGroup>
    <MinClientVersion>2.5</MinClientVersion>
</PropertyGroup>

And with that, you can now augment the build props and tasks in your consumers' projects, and use this approach to include any arbitrary files into your NuGet package.

Metadata properties

The nice thing about using a csproj, is that with this one file you can set all your assembly metadata, as well as NuGet package metadata.

The Additions to the csproj format page gives a great overview of (among other things) all the possible properties you can put into your csproj files, and where exactly these will go into your file NuGet package metadata. This is the final piece that ensures you don't actually have to hand-craft a nuspec file at all and can keep all your pack information inside the csproj file instead.

Summary

If you follow everything here, you should get a csproj file that:

  • Contains all your project and package metadata
  • Can include, or exclude a compiled DLL from source code
  • Can include contentFiles including Compile sources, and EmbeddedResource files
  • Can include build props and targets to condition how the consumer's build will run

Happy Hacking those NuGet packages!