Disable transitive project reference in .NET Standard 2

Transitive project references (ProjectReference)

Transitive project references are new feature of SDK-style csproj (1,2) format used in .NET Core/.NET >= 5. You can also use this csproj for old .NET Framework projects (1,2,3) but with some exceptions.

In this SDK-style format project references (represented by <ProjectReference> entry in .csproj file) are transitive. This is different to old non-SDK .csproj used previously.

But you have three options to go back to old non-transitive behavior.

  1. Use <DisableTransitiveProjectReferences>true</DisableTransitiveProjectReferences> property in .csproj that references projects for which you don't want their transitive dependencies to be visible by compiler.

    In your case you can add it to Web project. (first project that reference other projects, Web -> Service -> Business)

    You can also set this behavior globally for all .csprojs by doing it in Directory.Build.props file that you put in the root folder that contains your source.

     <Project>
       <PropertyGroup>    
         <DisableTransitiveProjectReferences>true</DisableTransitiveProjectReferences>
       </PropertyGroup>
     </Project>
    

    With this file you basically have old project reference behaviour. Useful when you do migration of old .NET Framework solution that uses old csproj format to new SDK-style .csprojs.

  2. On the project that you reference you can set which dependencies shouldn't flow further when the project is referenced. You use PrivateAssets="All" attribute on <ProjectReference> for this. So for example you can edit Service.csproj like this:

    <ItemGroup>
        <ProjectReference Include="..\Business.csproj" PrivateAssets="All" />
    </ItemGroup>
    

    This is more flexible and fine-grained approach. You can control with particular transitive project references should be visible when the project is referenced.

  3. Use ItemDefinitionGroup to define default <PrivateAssets>all</PrivateAssets> metadata for all ProjectReferences. You can also define it in Directory.Build.props file if you want to apply it globally to all projects.

    <ItemDefinitionGroup>
        <ProjectReference>
            <PrivateAssets>all</PrivateAssets>
        </ProjectReference>
    </ItemDefinitionGroup>
    

    The effect would be the same as setting <ProjectReference Include="..."> <PrivateAssets>All</PrivateAssets> </ProjectReference> (or PrivateAssets="All" attribute on all ProjectReferences entries.

    The difference from using 1. approach (DisableTransitiveProjectReferences) is that if you explicitly define PrivateAssets metadata on ProjectReference item then this value is used. You can think of using ItemDefinitionGroup as a way to provide default value of PrivateAssets metadata it it is not provided explicitly.

What should you use? It depends what you prefer. If you are used to old csproj behavior or want to migrate old solution to .NET Core then using DisableTransitiveProjectReferences or ItemDefinitionGroup in your Directory.Build.props is the easiest solution.


Nuget references (PackageReference) are also transitive by default.

This is not strictly answering your question but is something that you should be aware too.

If you are using new PackageReference format for nuget packages (and you probably do because this is the default in new SDK-style csproj files) then you should also be aware that these references are transitive. If your project references another project (with ProjectReference) that references nuget package (with PackageReference) then your project will also reference this nuget package.

Diagram showing ProjectA referencing ProjectB which references Newtonsoft.Jon package

Here ProjectA will have implicit reference to Newtosoft.Json library.

Unfortunately there is no DisableTransitivePackagesReferences for package references. But you can use PrivateAssets metadata like you did for ProjectReference in 2nd option or use ItemDefinitionGroup like you did in 3rd option.

That's why if you want to disable transitive dependencies both for project and package references for all projects then this is how your Directory.build.props file should look like:

<Project>
    <ItemDefinitionGroup>
        <ProjectReference>
            <PrivateAssets>all</PrivateAssets>
        </ProjectReference>
        <PackageReference>
            <PrivateAssets>all</PrivateAssets>
        </PackageReference>
    </ItemDefinitionGroup>
</Project>

(source: I learned about this technique from this blog )

or

<Project>
    <ItemDefinitionGroup>
        <DisableTransitiveProjectReferences>true</DisableTransitiveProjectReferences>
        <PackageReference>
            <PrivateAssets>all</PrivateAssets>
        </PackageReference>
    </ItemDefinitionGroup>
</Project>

All the above answers currently do not work for nuget package dependencies.

For the following library project (PrivateAssets="all"):

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

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
    <Authors>Roy</Authors>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Newtonsoft.Json" Version="13.0.1" PrivateAssets="all" />
  </ItemGroup>

</Project>

The following nuspec file will be produced:

<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
  <metadata>
    <id>MyLib</id>
    <version>1.0.0</version>
    <authors>Roy</authors>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>Package Description</description>
    <dependencies>
      <group targetFramework=".NETStandard2.0" />
    </dependencies>
  </metadata>
</package>

Notice how "Newtonsoft.Json" is not even specified as a dependency. This will obviously not work as Visual Studio will not even be aware of that dependency and the consuming project will fail to resolved "Newtonsoft.Json" at runtime.

The correct answer is setting PrivateAssets="compile".

Which will result in the following nuspec file:

<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
  <metadata>
    <id>MyLib</id>
    <version>1.0.0</version>
    <authors>Roy</authors>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>Package Description</description>
    <dependencies>
      <group targetFramework=".NETStandard2.0">
        <dependency id="Newtonsoft.Json" version="13.0.1" include="Runtime,Build,Native,ContentFiles,Analyzers,BuildTransitive" />
      </group>
    </dependencies>
  </metadata>
</package>

This will prevent the consuming project from accessing the Newtonsoft.Json namespace in code, while compiling and copying all the required assemblies to allow the "encapsulating" library work.


Well my question was close to one marked as duplicate here but to solve it requires different tactic.

Thanks to comment from "Federico Dipuma" and the answer given here I was able to solve this problem.

You should edit the Service.csproj file and add PrivateAssets="All" to ProjectReference keys you don't want to flow to top.

<ItemGroup>
    <ProjectReference Include="..\Business.csproj" PrivateAssets="All" />
</ItemGroup>