相关文章推荐
Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

Copy files from Nuget package to output directory with MsBuild in .csproj and dotnet pack command

Ask Question

Last time I had to find out how to extract some files from a Nuget package in took me at least 6 months but I finally managed to find the solution .

The thing is that, this solution assumes I have a .nupkg file and manually add a .targets file to perform the extraction process.

Now, things are different:

  • I don't have any .nupgk file, we generate one automatically on our VSTS server using the dotnet pack command. Then we consume the package from our Nuget server
  • We can't afford to take another 6 months to find the solution
  • Here is my ProjectName.csproj

    <Project Sdk="Microsoft.NET.Sdk">
        <PropertyGroup>
            <TargetFramework>netstandard2.0</TargetFramework>
            <RestoreProjectStyle>PackageReference</RestoreProjectStyle>
            <Authors>Jérôme MEVEL</Authors>
            <Version>1.0.3</Version>
            <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
            <!-- This answer got many votes on a Github thread so I tried just in case -->
            <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
            <!-- Just trying that too-->
            <IncludeBuildOutput>true</IncludeBuildOutput>
            <IncludeContentInPack>true</IncludeContentInPack>
            <!-- I wanted to see the full generated Nuget package -->
            <IncludeSource>true</IncludeSource>
            <!-- desperate attempt -->     
            <TargetsForTfmSpecificBuildOutput>GetMyPackageFiles</TargetsForTfmSpecificBuildOutput>
        </PropertyGroup>
        <ItemGroup>
            <PackageReference Include="Dapper" Version="1.50.5" />
            <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.1.1" />
            <PackageReference Include="Microsoft.Extensions.Logging" Version="2.1.1" />
            <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.1.1" />
            <PackageReference Include="NLog" Version="4.5.8">
                <!-- Let's try this too just in case-->
                <IncludeAssets>all</IncludeAssets>
            </PackageReference>
            <PackageReference Include="NLog.Extensions.Logging" Version="1.2.1">
                <IncludeAssets>all</IncludeAssets>
            </PackageReference>
            <PackageReference Include="NLog.Web.AspNetCore" Version="4.6.0">
                <IncludeAssets>all</IncludeAssets>
            </PackageReference>
            <PackageReference Include="System.Data.Common" Version="4.3.0" />
            <PackageReference Include="System.Data.SqlClient" Version="4.5.1" />
        </ItemGroup>
        <ItemGroup>
            <!-- Added the file to the root folder in case <IncludeAssets>all</IncludeAssets> is looking there -->
            <Content Include="NLog.config">
                <Pack>true</Pack>
                <PackagePath>NLog;;</PackagePath>
                <CopyToOutputDirectory>Always</CopyToOutputDirectory>
            </Content>
            <!-- My desired path to look for the file -->
            <Content Include="NLog\NLog.config">
              <Pack>true</Pack>
              <PackagePath>NLog;;</PackagePath>
              <CopyToOutputDirectory>Always</CopyToOutputDirectory>
            </Content>
        </ItemGroup>
        <!-- This is the default setting, perfectly working when I reference the project instead of installing the Nuget package -->
        <ItemGroup>
            <None Update="NLog\NLog.config">
                <CopyToOutputDirectory>Always</CopyToOutputDirectory>
            </None>
        </ItemGroup>
        <!-- desperate attempt -->
        <Target Name="GetMyPackageFiles">
            <ItemGroup>
                <BuildOutputInPackage Include="NLog/Nlog.config">
                    <TargetPath>NLog</TargetPath>
                </BuildOutputInPackage>
            </ItemGroup>
        </Target>
    </Project>
    

    As you can see I tried several different settings. This MsBuild results in a NLog.config file included in a NLog folder at the root of the Nuget package file.

    During my different tries, depending of the configuration I set, I was able to end-up with the NLog.config file at src/ProjectName.Logging/NLog/NLog.config or even at lib/netstandard2.0/Nlog.config.

    So my file is definitely included in my Nuget package file but isn't copied in the output directory of the project that consumes the package.

    I tried to specify a .nuspec file when generating my package with dotnet pack like described here but I was never able to get a desired result (either only my NLog.config was included in the Nuget package or all the source files). Moreover, this has several downsides like overriding the configuration in the .csproj file or adding useless complexity. I believe what I want to achieve could be done without using a .nuspec file (maybe I'm wrong).

    I noticed the build/ProjectName.targets file is missing in my package and this is probably the missing piece. So how to add this .targets file without manually modifying the package?

    Is there another way to copy my config file out of the Nuget package to the output directory?

    I really hope someone could help me solve this issue. This is the 2nd time I want to perform the same operation but with a slight difference and once again this is hard to do.

    Thanks a lot

    It is possible to copy files without the .nuspec file, if you create your nuget from the .csproj file as described here.

    And to copy files from nuget to output directory, create a ProjectName.targets file with the following content:

    <ItemGroup>
        <LogFiles Include="$(MSBuildThisFileDirectory)\..\contentFiles\LogFiles\*.config" />
    </ItemGroup>
    <Target Name="CopyLogFiles" BeforeTargets="Build">
        <Copy SourceFiles="@(LogFiles)" DestinationFolder="$(TargetDir)CopiedLogFiles\" />
    </Target>
    

    In your .csproj file add:

    <ItemGroup Label="FilesToCopy">
       <Content Include="ProjectName.targets" PackagePath="build/ProjectName.targets" />
       <Content Include="LogFiles\*.config" Pack="true" PackagePath="contentFiles\LogFiles">
         <PackageCopyToOutput>true</PackageCopyToOutput>
       </Content>
    </ItemGroup>
    

    The paths and names can of course be freely chosen.

    This should copy all .config files to a folder called CopiedLogFiles in the output directory!

    Thanks for your answer. I just tested it and it works well with dotnet pack both with PackageReference and Packages.config. Slight difference with my solution: your way doesn't create a symbolic link in the solution explorer in Visual Studio, which is not a bad thing in my opinion. – Jérôme MEVEL Mar 28, 2019 at 1:12 I'm accepting this answer as I think it's a better solution than mine. With the new PackageReference it's in my opinion, better not to have any symbolic link appearing in Visual Studio Explorer to the file physically located in the Nuget cache. Otherwise any change to this file inside Visual Studio would also change it for any other projects on the machine referencing the same Nuget package and same version, which is most likely not the desired behavior developers would like – Jérôme MEVEL Feb 6, 2020 at 13:00 What template/item type to choose while creating this target file? (Like in project I right click then Add->NewItem, then what to choose) – KBNanda Apr 15, 2020 at 16:34 I think there is no template for target files! They are common xml files. Here are some examples. – wmorian May 18, 2020 at 8:44

    Ok I finally found the solution and that includes a .nuspec file and a .targets file as well.

    The ProjectName.csproj just needs to include this

    <Project Sdk="Microsoft.NET.Sdk">
        <PropertyGroup>
            <TargetFramework>netstandard2.0</TargetFramework>
            <RestoreProjectStyle>PackageReference</RestoreProjectStyle>
            <NuspecFile>ProjectName.Logging.nuspec</NuspecFile>  
        </PropertyGroup>
        <!-- This is just for some projects referencing this project directly instead of the Nuget package -->
        <ItemGroup>
            <Content Include="NLog\NLog.config">
                <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
            </Content>
        </ItemGroup>
        <ItemGroup>
            <PackageReference Include="Dapper" Version="1.50.5" />
            <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.1.1" />
            <PackageReference Include="Microsoft.Extensions.Logging" Version="2.1.1" />
            <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.1.1" />
            <PackageReference Include="NLog" Version="4.5.8" />
            <PackageReference Include="NLog.Extensions.Logging" Version="1.2.1" />
            <PackageReference Include="NLog.Web.AspNetCore" Version="4.6.0" />
            <PackageReference Include="System.Data.Common" Version="4.3.0" />
            <PackageReference Include="System.Data.SqlClient" Version="4.5.1" />
        </ItemGroup>
    </Project>
    

    In the ProjectName.nuspec you put everything related to the Nuget package

    <?xml version="1.0"?>
    <package >
        <metadata>
            <id>ProjectName.Logging</id>
            <version>1.1.0</version>
            <authors>Jérôme MEVEL</authors>
            <description>Just a logging component</description>
            <releaseNotes>Extract the NLog.config file automatically</releaseNotes>
            <dependencies>
                <dependency id="Dapper" version="1.50.5" />
                <dependency id="Microsoft.Extensions.DependencyInjection" version="2.1.1" />
                <dependency id="Microsoft.Extensions.Logging" version="2.1.1" />
                <dependency id="Microsoft.Extensions.Logging.Abstractions" version="2.1.1" />
                <dependency id="NLog" version="4.5.8" />
                <dependency id="NLog.Extensions.Logging" version="1.2.1" />
                <dependency id="NLog.Web.AspNetCore" version="4.6.0" />
                <dependency id="System.Data.Common" version="4.3.0" />
                <dependency id="System.Data.SqlClient" version="4.5.1" />
            </dependencies>
        </metadata>
        <files>
            <file src="bin\Release\netstandard2.0\ProjectName.Logging.dll" target="lib/netstandard2.0/ProjectName.Logging.dll" />    
            <file src="ProjectName.Logging.targets" target="build/ProjectName.Logging.targets" />
            <file src="NLog/Nlog.config" target="content/Nlog.config" />
        </files>
    </package>
    

    And finally the ProjectName.targets. Careful! The file is located in the Nuget cache of the machine. You will be able to see it at the root of your project in Visual Studio but not in the Windows Explorer (in Windows at least). So if you modify the file in Visual Studio, it will modify it for ALL other projects on this machine referencing the same Nuget package (and same version).

    <?xml version="1.0" encoding="utf-8"?>
    <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
        <ItemGroup>
            <Content Include="$(MSBuildThisFileDirectory)\..\content/NLog.config">
                <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
            </Content>
        </ItemGroup>
    </Project>
    

    I generate the Nuget package using the dotnet pack nuget pack command (now that I've more experience I know that dotnet pack doesn't work well with a .nuspec file, there are several bugs) Here is the result:

    Finally I can just install my package and during the build process, the Nlog.config file will be copied out of the Nuget cache to the output directory of my project.

    I think that How do you set nuget contentFiles CopyToOutput value to true when using a .Net Standard library .csproj? provides a better answer to this question. https://github.com/NuGet/NuGet.Client/pull/1450

    You can set PackageCopyToOutput to true in the .csproj to declare the nuget content file as "CopyToOutput=true". That way any project referencing the nuget will have the content file marked with <CopyToOutput>true</CopyToOutput> in the referring csproj file, instructing msbuild to copy the content file to the ouput directory:

    In the .csproj of the nuget project:

    <Content Include="...">
        <PackageCopyToOutput>true</PackageCopyToOutput>
    </Content>
            

    Thanks for contributing an answer to Stack Overflow!

    • Please be sure to answer the question. Provide details and share your research!

    But avoid

    • Asking for help, clarification, or responding to other answers.
    • Making statements based on opinion; back them up with references or personal experience.

    To learn more, see our tips on writing great answers.

     
    推荐文章