Copy NuGet Content Files to Output Directory on Build

Recently someone posted an issue to my EF Core Handlebars project about an error they were receiving. After reproducing the issue, I narrowed it down to a problem copying template files to the project output directory on build. Because the file was missing from its intended location, my program was throwing an exception when it could not find the file.

This seemed like a strange error to me, because after over 267,000 package downloads, this was the first time I could recall someone reporting this error. However, this could be explained by the fact that most developers who use my package do so from Visual Studio, which seems to perform some magic copying package files to the output directory. Nevertheless, the error does take place consistently when running the EF Core scaffolding command from a terminal.

To correct the anomaly, I first needed to ensure the CodeTemplates folder was properly packaged in the NuGet file. The way I had been adding files was to include an ItemGroup in the project .csproj file in which I specified both the Content and PackagePath elements.

<ItemGroup>
<Content Include="CodeTemplates\CSharpDbContext\DbContext.hbs" PackagePath="lib\netstandard2.1\CodeTemplates\CSharpDbContext\" />
<Content Include="CodeTemplates\CSharpDbContext\Partials\DbImports.hbs" PackagePath="lib\netstandard2.1\CodeTemplates\CSharpDbContext\Partials\" />
<Content Include="CodeTemplates\CSharpDbContext\Partials\DbConstructor.hbs" PackagePath="lib\netstandard2.1\CodeTemplates\CSharpDbContext\Partials\" />
<Content Include="CodeTemplates\CSharpDbContext\Partials\DbOnConfiguring.hbs" PackagePath="lib\netstandard2.1\CodeTemplates\CSharpDbContext\Partials\" />
<Content Include="CodeTemplates\CSharpDbContext\Partials\DbSets.hbs" PackagePath="lib\netstandard2.1\CodeTemplates\CSharpDbContext\Partials\" />
<!– Remaining items elided for clarity –>

Viewing v5.0.4 of the .nupkg file in the NuGet Package Explorer, I could see that the CodeTemplates folder was placed as specified under the target framework folder of netstandard2.1.

While this worked functionally, the best practice starting with NuGet 3.3 has been to utilize the contentFiles feature to include static files in a NuGet package. In addition, rather than listing each file, I could specify the content in a single line with a file globbing pattern.

<ItemGroup>
<Content Include="CodeTemplates/**/*.*" Pack="true" PackagePath="contentFiles/CodeTemplates">
<PackageCopyToOutput>true</PackageCopyToOutput>
</Content>
</ItemGroup>

This resulted in the CodeTemplates folder being placed under contentFiles in the NuGet package, which is precisely where I wanted it to be.

Now that the CodeTemplates folder was in the correct location in the NuGet package, I could turn my attention to the task of copying the folder to the project output directory on build.

It turns out, to no surprise, that including content files in a NuGet package will not result in them being automatically copied to the project output directory when the developer builds the project. For this to happen, a custom build target is required, in which MS Build is explicitly instructed to copy files from one place to another on build. So I created a .targets file prefixed with the name of my library’s project name, and I added the a Target to copy the CodeTemplates folder from the NuGet contentFiles directory to the build target directory. In order to copy the entire folder structure, I also needed to include %(RecursiveDir) in the path.

<Project>
<ItemGroup>
<Files Include="$(MSBuildThisFileDirectory)/../contentFiles/CodeTemplates/**/*.*" />
</ItemGroup>
<Target Name="CopyFiles" AfterTargets="Build">
<Copy SourceFiles="@(Files)" DestinationFolder="$(TargetDir)/CodeTemplates/%(RecursiveDir)" />
</Target>
</Project>

Lastly, I added the following line in an ItemGroup of the project’s .csproj file, which placed the .targets file in the build folder of the NuGet package.

<Content Include="EntityFrameworkCore.Scaffolding.Handlebars.targets" PackagePath="build/EntityFrameworkCore.Scaffolding.Handlebars.targets" />

To verify that the CodeTemplates folder was copied to the expected location on build, I ran the following commands from a Mac terminal.

dotnet new classlib –name FolderCopyTest
cd FolderCopyTest
dotnet add package EntityFrameworkCore.Scaffolding.Handlebars –version 6.0.0-preview3
dotnet build
cd bin/Debug/net6.0
ls -R

I hope that sharing my journey of discovery will help someone seeking to perform the same kind of task. Happy coding!

About Tony Sneed

Sr. Software Solutions Architect, Hilti Global Application Software
This entry was posted in Technical and tagged . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.