Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • JavaScript • Angular
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

.NET Core SDK Projects: Controlling Output Folders and Dependencies Output


:P
On this page:
Edit this Post

last updated July 26th, 2022. Original post written on NetCore 3.0, updated for .NET 6.0

In my last post I talked about porting my Markdown Monster WPF application to .NET Core 3.0 and one of the problems I ran into was how to deal with properly handling compilation of Addins. In Markdown Monster Addins compile into a non-standard folder in the main EXE's output folder, so when building the project I want my Addin to be pushed right into the proper folder hierarchy inside of the parent project so that I can run and debug my addins along with the rest of the application.

This used to be pretty easy in classic .NET projects:

  • Add NuGet or Project References
  • Mark each assembly reference's Copy Local settings
  • Include new dependencies with Copy Local True
  • Exclude existing dependencies with Copy Local False

In the new .NET SDK projects this is more complicated as there's no simple way to exclude dependencies quite so easily. Either everything but the primary assembly is excluded which is the default, or you can set a switch to copy dependencies which copies every possible dependency into the output folder.

Let's take a look.

Where does output go?

By default .NET SDK projects push compiled output into:

bin\Release\net60\win-x64

The reason for this more complex path that includes a target framework is that SDK projects can potentially have multiple targets defined using the <TargetFrameworks> element (note the extra s) so you can do:

<TargetFrameworks>net462;net60</TargetFrameworks>

The separate folder structure allows for both targets to get their own respective output folders when you build the project.

For my addins, I don't want output to go into the standard location, but rather I need to specify a custom location in my main application's Addins/AddinName folder using:

<PropertyGroup>
    <OutDir>$(SolutionDir)MarkdownMonster/bin/$(Configuration)/$(TargetFramework)/win-x64/Addins/Weblog</OutDir>
</PropertyGroup>

which pushes the output into the correct location:

Controlling Dependency Output: With or Without Dependencies

When adding a project reference to my main project from the addin I ran into a bit of a problem in regards to how dependencies are handled.

I started with importing the <ProjectReference> via Visual Studio which produces:

<ItemGroup>
  <ProjectReference Include="../../MarkdownMonster/MarkdownMonster.csproj"  />
</ItemGroup>

Once the project is referenced if you indirectly reference features that require dependencies for the Markdown Monster project, it'll just work as the references are passed through an available from the host application.

When you do this by default all transient dependencies are pulled into the project. Also all dependency assemblies from the imported project are output into the build output folder. Most of the time that is the behavior you want.

But in some cases - an addin being a common scenario - you may not want all the dependencies from the referenced project to end up in the output folder. Since I'm creating an Addin that runs in the context of the host project, there's no need to duplicate the dependencies in the output folder.

How do we prevent the transient dependencies from being output?

Referencing Transient Dependencies without Outputting them

If you need to explicitly reference components from the main project either as NuGet Packages or direct binary references, it's possible to mark them as Compile Only Included Assets. Compile-only in this context means they won't get copied to the output folder.

Here's what this looks like for dependent packages that are explicitly required by the addin project's code:

<ItemGroup>
    <!-- Transient References explcitly accessed by the Addin Project -->
    <PackageReference Include="MahApps.Metro" version="1.6.5">
      <!-- keeps assembly output from going into the OutDir -->
      <IncludeAssets>compile</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Dragablz" version="0.0.3.203">
      <IncludeAssets>compile</IncludeAssets>
    </PackageReference>    
    ...
    
    <!-- direct project dependencies should be output into OutDir -->
    <PackageReference Include="xmlrpcnet" version="3.0.0.266" />
    <PackageReference Include="YamlDotNet" version="6.0.0" />
</ItemGroup>

The MahApps.Metro and Dragablz package references are part of the Main Markdown Monster project so they should be available for access, but not output into the output folder.

The XmlRpcNet and YamlDotNet packages are direct references unique to this Addin project and should output to the OutDir which is the 'normal' behavior.

Note that <IncludeAssets> (and also <ExcludeAssets>) don't show up in the Visual Studio .csproj schema Intellisense, so no hints for you! You get to guess with the rest of us.

Project Reference: Too Many Assemblies - make it stop!

Since this works so smoothly with <PackageReference> you'd expect the behavior to be pretty much the same with <ProjectReference> but unfortunately the behavior here is a bit different and a lot less obvious.

If I do a plain project reference from my Addin project to the main Markdown Monster project and then build, I get an output folder that is a mess with every single dependency from the main project dumped into my Addin output folder:

Eeek. This is not what I want here.

Limiting Project Output to only the Current Project

This should be easy: Visual Studio has an option to Copy Local = false which works for References and prevents dependencies to be copied into the output folder. Rather it just copies the actual output of the project and any direct dependencies of that project to the output folder.

Unfortunately the default behavior doesn't work for <ProjectReference> entries. For projects referenced with Copy Local, Visual Studio creates:

<ItemGroup>
    <ProjectReference Include="../../MarkdownMonster/MarkdownMonster.csproj" Private="False" />
</ItemGroup>

which does not prevent dependencies to be included. This produces the same massively cluttered output:

Well at least not all of them - it will only prevents nested project references from being imported and output to the Output path. But it will not prevent nested NuGet packages or explicit nested references to be excluded.

ExcludeAssets="all" or IncludeAssets="compile" keep out Transient Dependencies

A lot of false starts later, I found the solution in the ExcludeAssets or IncludeAssets flags that determine how output is copied. These work in combination with the Private flag which is why I missed this solution previously.

The fix requires both Private and ExcludeAssets (or IncludeAssets):

<ItemGroup>
    <ProjectReference Include="../../MarkdownMonster/MarkdownMonster.csproj">
        <Private>false</Private>
        <ExcludeAssets>all</ExcludeAssets>
    </ProjectReference>
</ItemGroup>

Alternately you can also use <IncludeAssets> which produces the same result (in this case - not sure what the subtle differences are):

<ItemGroup>
    <ProjectReference Include="../../MarkdownMonster/MarkdownMonster.csproj">
        <Private>false</Private>
        <IncludeAssets>compile</IncludeAssets>
    </ProjectReference>
</ItemGroup>

This does the right thing which is produce only the compiled output from the current project, plus any direct references from the Addin project:

WebLogAddin.dll is the main Addin assembly, and the CookComputing and YamlDotnet DLLs are direct dependencies of the Addin project.

Yay this works.

All Together Now: Addin Project References

The key takeaway from all this is:

  • The Visual Studio add project Copy Local doesn't work as expected
  • It takes both:
    <Private>false</Private> and
    <IncludeAssets>compile</IncludeAssets>
    produce expected Copy Local behavior

To put all of this Addin project import together for the addin looks something like this:

  • Set explicit output path
  • Reference Packages from Main Project without pulling dependencies into output
  • Reference Main Project without pulling in dependencies into output

Here are the key items in the .csproj file:

<PropertyGroup>
    <OutDir>$(SolutionDir)MarkdownMonster/bin/$(Configuration)/$(TargetFramework)/win-x64/Addins/Weblog</OutDir>
</PropertyGroup>
<ItemGroup>
     <!-- Transient References explcitly accessed by the Addin Project -->
    <PackageReference Include="MahApps.Metro" version="1.6.5">
      <!-- keeps assembly output from going into the OutDir -->
      <IncludeAssets>compile</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Dragablz" version="0.0.3.203">
      <IncludeAssets>compile</IncludeAssets>
    </PackageReference>
    ...
    
    <!-- direct project dependencies: These will output should be output into OutDir -->
    <PackageReference Include="xmlrpcnet" version="3.0.0.266" />
    <PackageReference Include="YamlDotNet" version="6.0.0" />
    
    <!-- Make sure to use Pivate and ExcludeAssets in combination 
         to prevent dependency output into OutDir                        -->
    <ProjectReference Include="../../MarkdownMonster/MarkdownMonster.csproj" 
                      Private="false" 
                      ExcludeAssets="all" />
</ItemGroup>

Harder than it should be

This is a re-write of this post with the final solution that was found nearly 3 years later. This is way harder than it should be. I have no idea if this solution was available prior to the current .NET 6.0 release or not but even today finding out how to reference this functionality or even the ExcludeAssets flag is nearly impossible. Unless you know what you're looking for you're going to stumble across a lot of dead ends and outdated information from earlier versions of .NET or even earlier versions of .NET Core.

What I'm describing here is a bit of an edge case because of the way the addins are wired up in my application, which is the opposite of most other components (ie. referencing a main executable from a component rather than the other way around).

To be clear having all assemblies in the output folder doesn't break the application so the default settings work just fine. But by default you do end up with a bunch of duplicated assemblies that likely don't want and have to explicitly exclude using the steps provided in this post.

Hopefully this post helps pointing you in the right direction to make it easier to reference other components when you don't want their dependencies dumped into your binary path.

this post created and published with Markdown Monster
created with the evaluation version of Markdown Monster
Posted in .NET Core  

The Voices of Reason


 

Artur M.
May 01, 2019

# re: .NET Core 3.0 SDK Projects: Controlling Output Folders and Content

Hi. This is a great and very helpful post! One question, if you don't mind: in those Explorer-like screenshots, what program are you using? Thanks.


Ruslan
May 01, 2019

# re: .NET Core 3.0 SDK Projects: Controlling Output Folders and Content

Hi! I would not recommend to use Assembly Reference instead of Project Reference because of parallel builds of solution. E.g. if you has huge solution with ~100 projects and some of projects use Assembly Reference it can raise exceptions on build when that project is builded before referenced one. It can be randomly. So need to tune Project Dependencies in this case or avoid Assembly Reference.


Rick Strahl
May 02, 2019

# re: .NET Core 3.0 SDK Projects: Controlling Output Folders and Content

Yeah I see that even in this smaller project - however I can't get a project reference to output the right things in the output folder so for now I don't have a better solution unfortunately.


Joe
September 20, 2019

# re: .NET Core 3.0 SDK Projects: Controlling Output Folders and Content

For many years, Apple’s development offerings were hopelessly behind Microsoft’s. Nowadays, however, its shortcomings are mitigated precisely due of its shortcomings. It’s fairly easy to find up-to-date development resources, since Apple doesn’t move the cheese very much. On the other hand, development on the Microsoft platform is very confusing, very difficult to navigate and a fast moving target.

I can’t tell you the number of hours I’ve lost implementing a Microsoft prescribed solution only to find out it’s deprecated shortly thereafter. Even worse, imagine the horror to find out what you thought was “new” had been deprecated months ago. I don’t attribute these issues to Microsoft rapid progress on their core technologies, but to their horrible management of their developer documentation.

Searching Microsoft’s website is so horrible that Google becomes the only alternative. Google’s search results, however, are often stale, leaving you wonder if there’s some sabotage at play. The documentation you do find is often scattered among the search noise on forums, Stackoverflow and blog sites. It’s not uncommon to find important information left out of the official documentation, but posted on an official Microsoft team member’s blog.

This issue is common among open source projects too and it’s often worse. Developers, however, choose Microsoft development because it has the resources to properly support properly support their developer resources. Fortunately, I do see some changes in a good direction. The official documentation now alerts you that the documenation you’re viewing isn’t up-to-date and links you to the most recent one. Still, there’s still quite a bit to be done.


Rick Strahl
September 20, 2019

# re: .NET Core 3.0 SDK Projects: Controlling Output Folders and Content

@Joe - I don't know if I agree with the documentation aspect of your statement. The documentation over the last couple of years has improved so drastically it's not even in the same ballpark compared to what came before. True - it's not always complete to address edge cases such as this, but that would be impossible to fit into topics. That's not what help documentation is designed for. That's for specialized articles or blog posts such as this.

I do agree, though that Microsoft's constant gyrations in inventing new things to do the the same thing can be really frustrating especially when 90% of the new features are 100% worthwhile with the remainder either unproven or almost impossible to work around. It's a common problem but as you say Microsoft tends to be more agressive on this front than most.

I don't know the answer but in my experience I feel it's not that different on other platforms. Certain things are easier, certain things are harder. I think it comes from the sheer complexity of the platforms that it becomes increasingly difficult for individual developers to have all of it in their head and even the original developers not remembering all the pieces that went into the technology.


ruslan
November 03, 2019

# re: .NET Core 3.0 SDK Projects: Controlling Output Folders and Content

Hello,

how can I copy asp.net core wwwroot folder to MyWebProject\bin\Debug\netcoreapp3.0. It's not copied to output directory.


Rick Strahl
November 04, 2019

# re: .NET Core 3.0 SDK Projects: Controlling Output Folders and Content

@ruslan - the wwwroot folder and all the files in it by default are copied to the distribution folder unless you explicitly disabled this behavior in a Web project file. Look for a section in the .csproj file that references the wwwroot folder (if any) and make sure that's set to copied. Also if you're not <Project Sdk="Microsoft.NET.Sdk.Web"> then the defaults are different and the project is not a 'web' project and you have to then explicitly add the wwwroot to be copied.


Wade @ DNCT
December 30, 2019

# re: .NET Core 3.0 SDK Projects: Controlling Output Folders and Content

Just in response to Ruslan, I also had the same "issue" or more so confusion.

Rick is right in that when you do a dotnet publish, your wwwroot is copied by default. However I think what Ruslan is having issues with is on a build, the wwwroot not being copied to the debug folder which seems to be correct. The wwwroot is only copied when you do a full publish, but not when you do a "dotnet build" or "Ctrl+Shift+B".

Whether or not this is correct, I would question what you need the wwwroot in the debug folder for anyway, since if you are copying this to a remote server etc, then you should do a dotnet publish. And if you are debugging etc, then you should be using dotnet run etc.


Dean
January 29, 2020

# re: .NET Core 3.0 SDK Projects: Controlling Output Folders and Content

This page shows in a Google search when I try to find if it's possible to prevent the creation of the different country folders in the bin\NetCoreApp3.0 folder, so different question. Just now moving to core, and this bugs me. Also, the "Runtimes" folder with Unix subfolder, etc. Is it simple to prevent these? I'm currently working with ASP.Net Core app.


Rick Strahl
January 29, 2020

# re: .NET Core 3.0 SDK Projects: Controlling Output Folders and Content

@Dean - I don't think you can surpress those just like you can't surpress various libraries from creating resource folders if they have resources for different languages when an app is compiled.

You can clean that up post publishing/building though as part of your own publish to production process if it bothers you that much.


Dean
January 30, 2020

# re: .NET Core 3.0 SDK Projects: Controlling Output Folders and Content

Thanks, Rick. I should have also said that I've enjoyed reading your blog for many years now, and this is the first time I've posted. The extra folders aren't a huge deal, but since it would be easy to clean-up with a post-build event, I'll do that.


Cathei
October 02, 2020

# re: .NET Core 3.0 SDK Projects: Controlling Output Folders and Content

Actually, to mark your project reference private you can just write <Private>False</Private> inside of <ProjectReference> item. According to Microsoft Documentation, <ProjectReference> item will be transformed to <Reference>, thus any valid metadata for <Reference> may be valid.


Cathei
October 06, 2020

# re: .NET Core 3.0 SDK Projects: Controlling Output Folders and Content

Hi, Rick. So what I’m trying to say is you don’t have to convert <ProjectReference> to <Reference> manually for <Private> metadata. You can’t use <IncludeAssets> like you posted, but you can directly use <Private>. So,

<Reference Include="$(SolutionDir)MarkdownMonster\bin\$(Configuration)\$(TargetFramework)\MarkdownMonster.exe">
    <Private>false</Private>
</Reference>

Above setting is equivalent to:

<ProjectReference Include="$(SolutionDir)MarkdownMonster\MarkdownMonster.csproj">
    <Private>false</Private>
</ProjectReference>

Simpler!


Miguel
July 14, 2022

# re: .NET Core 3.0 SDK Projects: Controlling Output Folders and Content

Hi Rick,

I run into this problem some time ago an this post helped me sort out the output of a project using a similar plugin architecture when we moved to .net core.

This is all working fine when we build from Visual Studio which uses MSBuild.

We are now moving to containers and using multi stage builds in docker where we use dotnet build MySolution.sln to do the actual building.

For some reason dotnet build even on windows produces a different output to what MSBuild used to so we are again getting some extra files in the output folder. This also happens if you we use Rider as it uses dotnet build under the hood.

Do you have any pointers as to how to fix this again on dotnet build?

Thanks!


Rick Strahl
July 14, 2022

# re: .NET Core 3.0 SDK Projects: Controlling Output Folders and Content

@Miguel - Visual Studio uses a custom version of MSBuild that behaves differently than the version that's used in dotnet build from the command line. Other than what's in this post I don't know of another solution...


Rick Strahl
July 24, 2022

# re: .NET Core 3.0 SDK Projects: Controlling Output Folders and Content

@Cathei - yeah, it'd be nice if <projectReference> worked but unfortunately it doesn't. Setting <private>false</private> has no effect and still pulls in all dependencies of the referenced project, unlike the DLL reference.


West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2022