A project (original VS format) is converted to use Package References. Great.
Project References "don't support content". Questionable choice, although not debated here.
Some used packages contain "content" and cannot or will not be updated to use "content files". Hmm..
What semi-automated tooling / method can be used to copy the "content" from NuGet dependencies that "don't support contentFiles"+? Only applying to direct packages is fine.
+Naturally, one could manually open up each individual NuGet file and copy the contents. This question isn't about "why" the switch was made and/or any merits or trade-offs and/or how how packages "should" be authored. The question is about an automated or semi-automated method to be able to restore the "content" into a project's source tree.
It is possible to use "content"-based NuGet packages with original Visual Studio / MSBuild projects that have been converted to use Package References.
The presented solution can likely be amended for SDK-style projects as well. The feat is accomplished by utilizing the GeneratePathProperty
attribute of PackageReference
+. Using the generated Pkg*
path properties ensures valid paths to the referenced packages. (Note: if a package name contains .
, replace it with _
in the Pkg*
property name.)
First, add a GeneratePathProperty
to all the packages with content to copy or link.
<PackageReference Include="bootstrap" GeneratePathProperty="true">
<Version>Don't ask..</Version>
</PackageReference>
<PackageReference Include="AngularJS.Core" GeneratePathProperty="true">
<Version>..it's not great</Version>
</PackageReference>
Depending on needs there are two different approaches presented here. While these approaches can be used together, neither approach should be applied to "content files"-based NuGet packages.
With this approach it's expected that the files are added to source control. The following lines in the project can thus be un-commented (and re-commented) when updating packages to ensure that the newest NuGet package content.
It uses the Copy
Task to, well, copy the the content.
<ItemGroup>
<NugetContentToRestore Include="
$(Pkgbootstrap)\Content\**\*.*;
$(PkgAngularJS_Core)\Content\**\*.*;
" />
</ItemGroup>
<Target Name="BeforeBuild">
<Copy SourceFiles="@(NugetContentToRestore)"
DestinationFiles="@(NugetContentToRestore->'$(ProjectDir)\%(RecursiveDir)%(Filename)%(Extension)')" />
</Target>
If the ItemGroup is not commented out during normal development, the items will be included in the the project’s solution explorer root, which is a bit ugly. It’s also not possible to detect when "content" has been removed.
If none of the content is to be copied and/or checked into source control along with the project, it can also be linked in. This approach has the benefit of always linking in the latest content and does not pollute the solution explorer.
A local file explicitly included in the project will take precedence over the linked resources.
For a normal project, <CopyToOutputDirectory>
is sufficient and the linked files will go in the 'bin' output. However, this does not work for a Web Project which only understands files which are physically present in the project tree.
<ItemGroup>
<Content Include="$(Pkgbootstrap)\Content\**\*.*">
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(PkgAngularJS_Core)\Content\**\*.*">
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
For a Web Project, these links must be copied to local files: <CopyToOutputDirectory>
is not sufficient or required. Packages with content that should go into the bin output should use the approach above.
<ItemGroup>
<Content Include="$(Pkgbootstrap)\Content\**\*.*">
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
</Content>
<Content Include="$(PkgAngularJS_Core)\Content\**\*.*">
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
</Content>
</ItemGroup>
Then add a target to copy the linked files so they are picked up by the Web Project tooling. See Copying linked content files at each build using MSBuild. It may be relevant so skip certain links and add source control ignore entries.
<Target Name="CopyLinkedContentFiles" BeforeTargets="Build">
<Copy SourceFiles="%(Content.Identity)"
DestinationFiles="%(Content.Link)"
SkipUnchangedFiles='true'
OverwriteReadOnlyFiles='true'
Condition="'%(Content.Link)' != ''" />
</Target>
+This works in an up-to-date Visual Studio 2019 environment. It may not work in certain older MSBuild/NuGet configurations as Pkg*
property generation is required.