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>
, butIncludeAssets
is set to include all asset types by default anyway.) - The
contentFiles
are included per language, per framework, based on theTargetFramework
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 setPack="true"
on None and Compile elements as well for them to be included the same way. As well as BuildActionCompile
, there are a couple of others:Content
,None
,Embedded Resource
. The same way you can define a file to be one of these in yourcsproj
, the project that includes yourcontentFiles
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 forNone
, 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
includingCompile
sources, andEmbeddedResource
files - Can include build props and targets to condition how the consumer’s build will run
Happy Hacking those NuGet packages!