0

I have a .NET Standard 2.0 project that is being packed into a NuGet package upon build. (Using the .csproj approach to generate a package on build) This project contains a file ApiClientSettings.json, which I want to copy to the output directory of the consuming project.

I have the following in my .csproj:

  <ItemGroup>
    <Content Include="ApiClientSettings.json">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
      <PackageCopyToOutput>true</PackageCopyToOutput>
    </Content>
  </ItemGroup>

According to MS documentation, the PackageCopyToOutput attribute should do exactly what I want to do, but it does not seem to work when I install the package into a .NET Framework project. (Works perfectly for .NET Core project)

Am I missing anything else?

Here is my entire .csproj file:

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

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
    <IsPackable>true</IsPackable>
    <TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage</TargetsForTfmSpecificBuildOutput>
    <AllowedOutputExtensionsInPackageBuildOutputFolder>
      $(AllowedOutputExtensionsInPackageBuildOutputFolder);.json
    </AllowedOutputExtensionsInPackageBuildOutputFolder>
    <Version>1.0.0</Version>
    <AssemblyVersion>1.0.0.0</AssemblyVersion>
    <FileVersion>1.0.0.0</FileVersion>
    <IncludeContentInPack>true</IncludeContentInPack>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
    <OutputPath></OutputPath>
  </PropertyGroup>

  <ItemGroup>
    <Content Include="ApiClientSettings.json">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
      <PackageCopyToOutput>true</PackageCopyToOutput>
    </Content>
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
    <PackageReference Include="Microsoft.Extensions.Configuration" Version="3.1.5" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.5" />
    <PackageReference Include="Microsoft.Extensions.Http" Version="3.1.5" />
    <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\DbModelLibrary\DbModelLibrary.csproj">
      <ReferenceOutputAssembly>true</ReferenceOutputAssembly>
      <IncludeAssets>DbModelLibrary.dll</IncludeAssets>
    </ProjectReference>
  </ItemGroup>

  <Target DependsOnTargets="ResolveReferences" Name="CopyProjectReferencesToPackage">
    <ItemGroup>
      <BuildOutputInPackage Include="@(ReferenceCopyLocalPaths-&gt;WithMetadataValue('ReferenceSourceTarget', 'ProjectReference'))" />
    </ItemGroup>
  </Target>

</Project>

Here is my generated .nuspec file aswell:

<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
  <metadata>
    <id>Iso17025ApiClient</id>
    <version>1.0.0</version>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <dependencies>
      <group targetFramework=".NETStandard2.0">
        <dependency id="Microsoft.AspNet.WebApi.Client" version="5.2.7" exclude="Build,Analyzers" />
        <dependency id="Microsoft.Extensions.Configuration" version="3.1.5" exclude="Build,Analyzers" />
        <dependency id="Microsoft.Extensions.Configuration.Json" version="3.1.5" exclude="Build,Analyzers" />
        <dependency id="Microsoft.Extensions.Http" version="3.1.5" exclude="Build,Analyzers" />
        <dependency id="Newtonsoft.Json" version="12.0.3" exclude="Build,Analyzers" />
      </group>
    </dependencies>
    <contentFiles>
      <files include="any/netstandard2.0/appsettings.json" buildAction="Content" copyToOutput="true" />
    </contentFiles>
  </metadata>
</package>

I am adding the file structure of the consuming .NET Framework apps:

enter image description here

When the NuGet package reference is used, the file gets copied to the output directory, from the global package directory, but does not get added to the solution.

If the packages.config is used, the file gets copied to the solution but does not get marked as "Copy to Output" even though it is specified to do so in the NuGet package.

This probably has to do with the changes between pacakges.config and PackageReference NuGet package management? The difference between contentFiles and content probably.

What still confuses me, is that the .NET Core apps use PackageReference and the behaviour is different as with the .NET Framework apps. In the .NET Core apps the appsettings.json is added to the solution AND marked as "Copy to Output".

I would like the users to have roughly the same experience with the package no matter which target framework they are using. (I do not want them to manually add an appsettings.json JUST because they are using .NET Framework. Is this avoidable in some way?

1 Answers1

2

I have tried this in plain projects with these lines from your sample and it works.

<ItemGroup>
    <Content Include="ApiClientSettings.json">
        <CopyToOutputDirectory>Always</CopyToOutputDirectory>
        <PackageCopyToOutput>true</PackageCopyToOutput>
        <PackagePath>contentFiles\any\any\;content</PackagePath>
    </Content>
</ItemGroup>

However, there is something wrong with your generated NuSpec. This line in the contentFiles tag is wrong.

<files include="any/netstandard2.0/ApiClientSettings.json" buildAction="Content" copyToOutput="Always" />

According to your project file, instead of netstandard2.0 it should be any and the attribute for copyToOutput allows for true or false, but not Always. The files tag should look like this.

<files include="any/any/ApiClientSettings.json" buildAction="Content" copyToOutput="true" />

Maybe there are leftovers from testing your package and your caches are corrupted, because your NuSpec does not match the given project file. In Visual Studio you can clear the caches with Tools > Options > NuGet Package Manager > General > Clear all caches or delete your global .nuget folder contents.

Just for you to compare, this is the library project.

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
        <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
    </PropertyGroup>
    <ItemGroup>
        <None Remove="ApiClientSettings.json" />
    </ItemGroup>
    <ItemGroup>
        <Content Include="ApiClientSettings.json">
            <CopyToOutputDirectory>Always</CopyToOutputDirectory>
            <PackageCopyToOutput>true</PackageCopyToOutput>
            <PackagePath>contentFiles\any\any\;content</PackagePath>
        </Content>
    </ItemGroup>
</Project>

This is the PackageReference in both .NET Framework and .NET Core projects.

<ItemGroup>
    <PackageReference Include="ClassLibrary" Version="1.0.0" />
</ItemGroup>

The behavior you recognize between PackageReference and packages.config is because the latter does not support contentFiles. You will have to resort to using the content folder and a custom MS Build .targets file, like this.

I would like the users to have roughly the same experience with the package no matter which target framework they are using.

NuGet packages behave very different not only in terms of PackageReference and packages.config but also whether you use the old-style project format or the new SDK-style project format in your libraries and applications. Consequently, it is hard to provide the same experience in all cases. I recommend you to use SDK-style projects with PackageReference to gain access to the latest features.

thatguy
  • 21,059
  • 6
  • 30
  • 40
  • I have removed the 'any/any/ApiClientSettings.json' and left it as default. I changed the copyToOutput="Always" to copyToOutput="true". When I install the package into a .NET Framework app, the ApiClientSettings.json is correctly copied over, but the Copy To Output Directory is never set. (This works fine in a .NET Core project) – Stephan V.d Westhuizen Jun 30 '20 at 05:01
  • I have tried it with a .NET Framework 4.7.2 and a .NET Core 3.1 console application as well, both worked as expected. Added my project files for comparison. – thatguy Jun 30 '20 at 07:01
  • I edited my question to include some extra detail that I discovered between .NET Framework's PackageReference vs package.config behaviours. – Stephan V.d Westhuizen Jul 01 '20 at 07:25
  • @Stephan V.d Westhuizen I have updated the answer based on the newly provided information. – thatguy Jul 04 '20 at 13:22