Disable transitive project reference in .NET Standard 2
Asked Answered
B

3

38

I'm writing an MVC website using ASP.NET Core 2.0.

In the ASP.NET Core project (let's call it Web), I reference a .NET Standard 2 project in the same solution (let's call it Service). The Service project also references a third .NET Standard 2 library in the solution (let's call this one Business). The Business project declares a type called Model.

The problem is that I can use Model in the Web project (i.e. the compiler sees the type Model and I can do var a = new Model();) as if the Web project has referenced Business, but it actually only has a reference to Service.

How can I hide Model from Web? Is this a new feature in ASP.NET Core 2 or all .NET Standard projects are like this?

Edit

As specified here, this is due to transitive project references which is a new "feature" in .NET Standard, but how do I fix it?

Babiche answered 12/10, 2017 at 11:56 Comment(2)
I believe this is a feature of the new project formats (project.json and csproj now). Any nested reference is available directly inside the project (aka transitive dependencies). You may try this way to turn it off for a single reference.Cataphyll
Possible duplicate of Transitive references in .Net Core 1.1Cataphyll
B
22

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>
Babiche answered 12/10, 2017 at 12:27 Comment(1)
nice and clear - just to underline what you've said (but which i managed to overlook first time). Reference chain is Web->Services->Business and it is in SERVICES that you need to add the PrivateAssets="All" to hide Business from Web (I was putting it in the Web->Services ProjectReference which wasn't working - I just needed to read your answer more carefully!)Cupreous
D
52

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>
Dagmardagna answered 25/3, 2020 at 15:42 Comment(1)
Hey Mariusz, is there a way of doing it from the referencing project? This here modifies the project being referenced, not the project doing the referencing :/Reverential
B
22

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>
Babiche answered 12/10, 2017 at 12:27 Comment(1)
nice and clear - just to underline what you've said (but which i managed to overlook first time). Reference chain is Web->Services->Business and it is in SERVICES that you need to add the PrivateAssets="All" to hide Business from Web (I was putting it in the Web->Services ProjectReference which wasn't working - I just needed to read your answer more carefully!)Cupreous
R
11

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.

Renick answered 20/5, 2021 at 23:24 Comment(3)
For me i must use PrivateAssets="compile", with build i've access to namespaceStrader
@JuanR, seems you are right, I made so many tests I probably got confused by the local nuget package caching and the right solution. I corrected my answer. Thanks!Renick
This should be the accepted answer. With PrivateAssets="all", the runtime indicates a missing library, which this answer explains why.Lycanthropy

© 2022 - 2024 — McMap. All rights reserved.