169

I'm trying to write a plugin system with .NET Core, and one of my requirements are to be able to distribute the plugin DLL along with its dependencies to the user for install.

However, I can't figure out how to include my NuGet dependencies as a build artifact and have them output to the build folder, without having to use dotnet publish as a hack. Is there some way I can specify this in the .csproj file (project file)?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
chyyran
  • 2,446
  • 2
  • 21
  • 35
  • 3
    Why would using `dotnet publish` be a hack? Include the command in your csproj file as a post build script. – Austin Drenski May 07 '17 at 23:08
  • 8
    `dotnet publish` throws the entire framework in the publish folder, since I'm writing a plugin, the majority of the files are not necessary since the framework would already be loaded by the bootstrapper program. I'm looking for something similar to how builds work on .NET Framework. – chyyran May 07 '17 at 23:31
  • And including `Always` in your csproj on each of the dlls you want to move doesn't do the trick? Perhaps combined with a `` node? – Austin Drenski May 08 '17 at 01:53
  • 11
    `` does not support ``. – chyyran May 08 '17 at 04:23
  • 2
    The "entire framework" comes from NuGet though.. and if you opt into copying all NuGet assemblies to the build output, you will get all of them.. – Martin Ullrich May 08 '17 at 06:55
  • 3
    @AustinDrenski - I tried your suggestion and can only strongly disagree in adding dotnet publish as a post build event as dotnet publish is also rebuilding the project which leads to a build recursion. – Romout Jul 03 '19 at 12:56
  • I thought I needed to configure something to copy, but I should have first searched my `bin/` dir because the files were already copied by default! – Carl Walsh Oct 12 '21 at 18:22
  • In my experience they are copied by default for an exe but not a class library. – Denise Skidmore May 23 '23 at 17:42

6 Answers6

245

You can add this to a <PropertyGroup> inside your csproj file to enforce copying NuGet assemblies to the build output:

<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

However, note that the build output (bin/Release/netcoreapp*/*) is not supposed to be portable and distributable, the output of dotnet publish is. But in your case, copying the assemblies to the build output is probably very useful for testing purposes. But note that you could also use the DependencyContext api to resolve the DLLs and their locations that are part of the application's dependency graph instead of enumerating a local directory.

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
Martin Ullrich
  • 94,744
  • 25
  • 252
  • 217
  • 9
    It causes copy all dlls, not just Nuget dlls – Mohammad Dayyan Aug 24 '17 at 19:29
  • dlls from referenced projects are copied always, dlls coming in via nuget should be the only additional ones via this mechanism.. @Mohammad Dayyan which additional files do you see? (note that even System.* dlls actually do come in via nuget) – Martin Ullrich Aug 24 '17 at 19:48
  • 4
    Core 2 I am getting all the Microsoft DLL's too. Not sure why but before I was getting NuGet only but then it stopped doing it? annoying – Piotr Kula Sep 13 '17 at 08:56
  • 4
    @MartinUllrich Can you elaborate on the `DependencyContext`? How can I use it to find a DLL that's not in the application directory? Where is it anyway? – ygoe Nov 30 '17 at 21:46
  • 1
    "However, note that the build output is not supposed to be portable and distributable, the output of dotnet publish is." Frankly, I don't care what they think things should be. They broke the VS test runners with this (NUnit DLLs are not copied.) – jpmc26 Sep 04 '18 at 18:22
  • 1
    VS test runners should work with the right packages as long as these packages "do the right ting". Use `dotnet new nunit` to create an NUnit test project. – Martin Ullrich Sep 04 '18 at 21:04
  • 2
    not work for me asp.net core not copying System.ValueTuple.dll – Ali Yousefi Nov 18 '18 at 08:03
  • 1
    @AliYousefie system.valuietuple for .net framework projects should no longer come from NuGet on recent .net framework versions and build tooling – Martin Ullrich Nov 18 '18 at 08:58
  • @MartinUllrich i don't now i had some exception "system.valuetuple dll not exist" but after install net core sdk problems gone – Ali Yousefi Nov 18 '18 at 10:01
  • 2
    _"However, note that the build output (bin/Release/netcoreapp*/*) is not supposed to be portable and distributable, the output of dotnet publish is"_ How then is this supposed to work when I go to debug something and get a file not found exception because the nuget packagereference doesnt get copied to output. This part cannot be true – StingyJack May 29 '22 at 01:35
  • Should this also work for a project dependency? I.e., if I have CopyLocalLockFileAssemblies set in both parent and child project, should I expect child's references being copied to the parent build output? Because currently it is not doing that. – aleksander_si Dec 12 '22 at 08:13
20

You can use PostBuildEvent to automate module deployment on build.

To get NuGet assemblies in build folder add in csproj of your module

<PropertyGroup>
    <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>

Define what module files you want where using Include/Exclude (modify path as necessary)

<ItemGroup>
    <ModuleFiles
      Include="$(TargetDir)*.dll"
      Exclude="$(TargetDir)System*.dll;$(TargetDir)Microsoft*.dll"
      DestinationPath="$(SolutionDir)src\MyProject\Modules\MyModule\%(Filename)%(Extension)">
    </ModuleFiles>
</ItemGroup>

Reset your build folder to default and add PostbuildEvent

<Target Name="PublishModule" AfterTargets="PostBuildEvent" Inputs="@(ModuleFiles)" Outputs="@(ModuleFiles->'%(DestinationPath)')">
    <WriteLinesToFile File="$(SolutionDir)src\[YOURAPP]\app_offline.htm" />
    <Copy SourceFiles="@(ModuleFiles)" DestinationFiles="@(ModuleFiles->'%(DestinationPath)')" />
    <Delete Files="$(SolutionDir)src\[YOURAPP]\app_offline.htm" />
</Target>

I'm including app_offline to recycle app if it's already running to avoid file in use errors.

Xeevis
  • 4,325
  • 25
  • 26
  • In my project, I have a dependency on "Microsoft.Extensions.Logging.Log4Net.AspNetCore" Nuget library, and it's not a part of NetCore, so this approach won't work – aluky Apr 13 '18 at 10:42
13

Adding

<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

didn't work, but adding this to the Framework .csproj file:

<RestoreProjectStyle>PackageReference</RestoreProjectStyle>

did.

Mike Brunner
  • 402
  • 3
  • 11
  • This worked well for me when I was referencing .net Standard 2.0 libraries from within a .Net Framework 4.7.2 project. Nothing else fixed it. – Grungondola Aug 07 '19 at 13:12
  • In my case i am using Serilog, Serilog.Console, Serilog.Files as nuget reference to project A, and project B references project A. Without the RestoreProjectStyle element some Serilog, Serilog.Console is copied, but Serilog.Files.dll is not copied. After adding to project A, project B, it started working. I suppose you just need that for project A. – Soundararajan Oct 27 '21 at 09:52
  • This didn't work for me either – Michael Brown Dec 01 '21 at 23:39
  • Same here - the `` did not work for a .NET Framework 4.6.1 application. – Doug Kimzey Oct 27 '22 at 12:03
10

I am using .NET 5 and here is my solution to my similar issue.

Structure: Project-A (Contained Selenium Nuget References, and selenium code) Project-B (A unit test project, which calls methods in Project-A)

Issue: When building the solution, the chromedriver.exe file was appearing in the Project-A bin folder, but would not get copied to the Project-B bin folder, so the unit tests could not execute. An exception was thrown saying chromedriver.exe was not found.

Solution: Modify the attribute in Project-A for the Selenium ChromeDriver NuGet package reference to only consider 'contentfiles;analyzers' as private assets. The default value for this is 'contentfiles;analyzers;build' when not specified. This now means it is okay to flow the output files of the build to parent referencing projects, but not contentfiles or analyzers, where as 'build' was also previously considered a private asset and would not flow through to parent projects.

Before (in Project-A.csproj):

<ItemGroup>
  <PackageReference Include="Selenium.Support" Version="3.141.0" />
  <PackageReference Include="Selenium.WebDriver" Version="3.141.0" />
  <PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="87.0.4280.8800" />
</ItemGroup>

After (in Project-A.csproj):

<ItemGroup>
  <PackageReference Include="Selenium.Support" Version="3.141.0" />
  <PackageReference Include="Selenium.WebDriver" Version="3.141.0" />
  <PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="87.0.4280.8800">
    <PrivateAssets>contentfiles;analyzers</PrivateAssets>
  </PackageReference>
</ItemGroup>

I found this information in this link: https://learn.microsoft.com/en-us/nuget/consume-packages/package-references-in-project-files#controlling-dependency-assets

Hope this helps someone! Good luck.

Jason Tarr
  • 321
  • 2
  • 4
5

I "solved" (created work around) this in simpler way.

In post build

dotnet publish "$(ProjectFileName)" --no-build -o pub
xcopy "$(ProjectDir)pub\3rdPartyProvider.*.dll" "$(OutDir)"

pub is the folder where you want your published stuff go for staging

NOTE: depending on what version of dotnet.exe you use, command --no-build may not be available.

For example, not available in v2.0.3; and available in v2.1.402. I know that VS2017 Update4 had v2.0.3. And Update8 has 2.1.x

Update:

The setup above will work in the basic debug environment but to put it into build server/production environment more is needed. In this particular example that I had to solve, we build Release|x64 and Release|x86 separately. So I accounted for both. But to support the post build dotnet publish command, I first added RuntimeIdentifier to project file.

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
  <OutputPath>..\..\lib\</OutputPath>
  <RuntimeIdentifier>win-x64</RuntimeIdentifier>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
  <OutputPath>..\..\lib\</OutputPath>
  <RuntimeIdentifier>win-x86</RuntimeIdentifier>
</PropertyGroup>

Why I needed it and why you can get away without it? I needed this because my build program is set to intercept warning MSB3270, and fail the build if it appears. This warning says, "hey, some files in your dependencies are of wrong format". But do you remember the goal of this exercise? We need to pull package dependency DLLs. And in many cases it doesn't matter if this warning is there because following post build does not care. Again, this is my build program that cares. So, I only added RuntimeIdentifier to 2 configurations I use during production build.

Full Post build

if not exist "$(ProjectDir)obj\$(ConfigurationName)" mkdir "$(ProjectDir)obj\$(ConfigurationName)"
xcopy  "$(ProjectDir)obj\$(PlatformName)\$(ConfigurationName)" "$(ProjectDir)obj\$(ConfigurationName)" /E /R /Y

if $(ConfigurationName) == Release (
    dotnet publish "$(ProjectFileName)" --runtime win-$(PlatformName) --no-build -c $(ConfigurationName) -o pub --no-restore --no-dependencies
) else (
    dotnet publish "$(ProjectFileName)" --no-build -c $(ConfigurationName) -o pub --no-restore --no-dependencies
)

xcopy "$(ProjectDir)pub\my3rdPartyCompany.*.dll" "$(OutDir)" /Y /R

Explanation: dotnet publish is looking for obj\Debug or obj\Release. We don't have it during the build because build creates obj\x64\Release or obj\x86\Release. Line 1 and 2 mitigate this issue. In line 3 I tell dotnet.exe to use specific configuration and target runtime. Otherwise, when this is debug mode, I don't care about runtime stuff and warnings. And in the last line I simply take my dlls and copy then into output folder. Job done.

T.S.
  • 18,195
  • 11
  • 58
  • 78
  • "-c Release" parameter is required for "dotnet publish" command if the project has no debug configuration (like in my case). So I used this batch as post-build event: `dotnet publish "$(ProjectFileName)" -c Release --no-build -o bin\pub` `xcopy "$(ProjectDir)pub\PostSharp.dll" "$(OutDir)"` – Xtro Jan 20 '19 at 18:27
1

In conjunction with the above answer: I've got this working great in the Post-build event command line: in Visual Studio. It loops over a selection of dlls (System*.dll and Microsoft.dll)*, and then skips the deletion of specific dlls. System.Data.SqlClient.dll and System.Runtime.Loader.dll

for %%f in ($(OutDir)System*.dll $(OutDir)Microsoft*.dll) do if not %%f == $(OutDir)System.Data.SqlClient.dll if not %%f == $(OutDir)System.Runtime.Loader.dll del %%f
seabass
  • 1,030
  • 10
  • 22