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
<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.
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
contentfilesAssets 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
IncludeAssetsis set to include all asset types by default anyway.)
contentFilesare included per language, per framework, based on the
TargetFrameworkyou 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:
Embedded Resource. The same way you can define a file to be one of these in your
csproj, the project that includes your
contentFileswill have them set to that specified item type.
Contentmight 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
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
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.
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.
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
- Can include build props and targets to condition how the consumer's build will run
Happy Hacking those NuGet packages!