Rick Strahl's Web Log

Wind, waves, code and everything in between...
ASP.NET • C# • HTML5 • JavaScript • AngularJs
Contact   •   Articles   •   Products   •   Support   •   Search
Ad-free experience sponsored by:
ASPOSE - the market leader of .NET and Java APIs for file formats – natively work with DOCX, XLSX, PPT, PDF, images and more

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


:P
On this page:
Edit this Post

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:

<projectRoot>bin\Release\netcore3.0

The reason for this more complex path that includes a target framework is that SDK projects can potentially have multiple targets defined in the <TargetFramework> element so you can do:

<TargetFrameworks>net462;netcore3.0</TargetFrameworks>

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

For my addins this is not what I want - I want to send output to a very specific folder in the 'parent' Exe project in the Addins\AddinName folder:

Not only that but I also need to write out only the actual assembly for the output plus any new dependencies that aren't already referenced in the main project - rather than all or no dependencies which are the 'default' options.

Sending output to a Custom Folder with Dependencies

So to send output to a non-default folder you can use <OutDir> and to force dependencies to be included in the output rather than the default behavior that just includes the project's assembly you can use <CopyLocalLockFileAssemblies>.

Here's what that looks like in my project:

<PropertyGroup>
    <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
    <OutDir>$(SolutionDir)MarkdownMonster\bin\$(Configuration)\$(TargetFramework)\Addins\Weblog</OutDir>
</PropertyGroup>

The <OutDir> element points at the Exe's project output folder and copies files directly into the specified folder without a target folder.

If you want to generate output to a new folder and get a target framework root folder there's the <OutputPath> directive.

<CopyLocalLockFileAssemblies> is a very blunt tool. It copies everything related to a dependency so it can produce a boatload of assemblies and content files that you likely don't want, so you likely will need to filter the resulting output.

The <CopyLocalLockFileAssemblies> ensures that all dependencies are copied, not just the one assembly generated for this project. So we need to filter the files somehow. More on that below.

With <OutDir> the output goes into the main project output folder depending on the current target framework (potentially multiples) and the Configuration which is Debug or Release most likely.

Ok - output's now going where it needs to go.

Controlling Output Assemblies

The next problem is that when I now build the project the project output includes all dependencies. That includes all NuGet package assemblies, all dependent assemblies, and also the dependencies for my Main EXE's reference:

Holy crap that's a lot of assemblies and all buy 3 of them are in this case duplicated.

So the next step is to NuGet packages and Assembly References from bringing in all of their dependencies.

For NuGet Packages the element to use is <IncludeAssets> and set the value compile:

<ItemGroup>
    <!-- Assemblies already referenced by mainline -->
    <PackageReference Include="MahApps.Metro" version="1.6.5">
      <IncludeAssets>compile</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Dragablz" version="0.0.3.203">
      <IncludeAssets>compile</IncludeAssets>
    </PackageReference>    
    ...
    
    <!-- my dependencies that aren't used by main project 
         so I'm not using `<IncludeAssets>`                 -->
    <PackageReference Include="xmlrpcnet" version="3.0.0.266" />
    <PackageReference Include="YamlDotNet" version="6.0.0" />
</ItemGroup>

The point of this to 'exclude' any of the dependencies that are already loaded by the main executable and so don't need to be redistributed again. The <IncludeAssets>compile</IncludeAssets>. The only packages that I actually want to be included in the output folder are those new assemblies that are not already loaded by the main Exe.

There's more info on the various <IncludeAssets> and related elements values that you can provide in the NuGet documentation.

Project or Assembly References also Copy Files

I'm still not done - I also have an assembly reference that points back at the main EXE project. My first try used a project reference, but this would pull in the entire project including all related assets. Ouch.

So this didn't work:

<ItemGroup>
    <ProjectReference Include="$(SolutionDir)MarkdownMonster\MarkdownMonster.csproj" >
     <IncludeAssets>compile</IncludeAssets>
    </ProjectReference>
</ItemGroup>  

I couldn't find a setting for <IncludeAssets> or <ExcludeAssets> that works for the Project Reference. No matter what I did the depedencies were copied in.

So - instead of a project reference I can also use an Assembly Reference instead pointing at the compiled EXE. Then I can mark it as Private which won't copy all of the project's content into the output folder:

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

Success. The end result of both the package references and project reference now is:

Just to summarize here's the complete project file for the WeblogAddin project:

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

  <PropertyGroup>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <AssemblyName>WeblogAddin</AssemblyName>
    <UseWPF>true</UseWPF>
    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>

    <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
    <OutDir>$(SolutionDir)MarkdownMonster\bin\$(Configuration)\$(TargetFramework)\Addins\Weblog</OutDir>

    <Authors>Rick Strahl, West Wind Technologies</Authors>
  </PropertyGroup>

  <ItemGroup>
    <!-- Assemblies already referenced by mainline -->
    <PackageReference Include="MahApps.Metro" version="1.6.5">
      <IncludeAssets>compile</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Dragablz" version="0.0.3.203">
      <IncludeAssets>compile</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" version="1.0.1">
      <IncludeAssets>compile</IncludeAssets>
    </PackageReference>
    <PackageReference Include="FontAwesome.WPF" Version="4.7.0.9">
      <IncludeAssets>compile</IncludeAssets>
    </PackageReference>
    <PackageReference Include="HtmlAgilityPack" version="1.11.3">
      <IncludeAssets>compile</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Newtonsoft.Json" version="12.0.1">
      <IncludeAssets>compile</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Westwind.Utilities" version="3.0.26">
      <IncludeAssets>compile</IncludeAssets>
    </PackageReference>

    <!-- my dependencies that aren't used by main project 
         so I'm not using `<IncludeAssets>`                 -->
    <PackageReference Include="xmlrpcnet" version="3.0.0.266" />
    <PackageReference Include="YamlDotNet" version="6.0.0" />
  </ItemGroup>

  <ItemGroup>
    <!--<ProjectReference Include="$(SolutionDir)MarkdownMonster\MarkdownMonster.csproj" >
     <IncludeAssets>compile</IncludeAssets>
    </ProjectReference>-->
    <Reference Include="$(SolutionDir)MarkdownMonster\bin\$(Configuration)\$(TargetFramework)\MarkdownMonster.exe">
      <Private>false</Private>
    </Reference>
  </ItemGroup>  

  <ItemGroup>
    <Resource Include="icon.png" />
    <Resource Include="icon_22.png" />
    <Resource Include="MarkdownMonster_Icon_128.png" />
  </ItemGroup>  

</Project>

Harder than it should be

What I'm describing here is a bit of an edge case because of the way the addins are wired up in my application, but it sure feels like these are a lot of hoops to jump through for behavior that used to work in classic projects by simply specifying an alternate output folder. I also find it very odd that all dependencies are pulled in from an assembly reference (my main Markdown Monster project DLL which references The World).

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.

In the end it all works and that that's the important thing, but it's a bit convoluted to make this work and wasn't easy to discover. A few pointers from Twitter is what got me over the hump.

And that's what this post is for - so I (and perhaps you) can come back to this and remember how the heck to get the right incantation to get just the right files copied into the output folder.

this post created and published with 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.


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