1

I have solution that uses clean architecture, so I have following projects:

  1. Core
  2. Application that depends on Core
  3. Infrastructure that depends on Application
  4. Web that depends on Application and Infrastructure

I need to create NuGet package so I have went to folder with my Web.csproj and I typed following command in PowerShell: .\nuget pack Web/Web.csproj -IncludeReferencedProjects

Seems that all should work, but when I install this NuGet package into another project I'm getting following warnings:

Warning NU1603 Web 1.0.0 depends on Infrastructure (>= 1.0.0) but Infrastructure 1.0.0 was not found. An approximate best match of Infrastructure 1.0.0.1 was resolved.

Warning NU1603 Web 1.0.0 depends on Application (>= 1.0.0) but Application 1.0.0 was not found. An approximate best match of Application 1.2.1 was resolved.

Warning NU1701 Package 'Infrastructure 1.0.0.1' was restored using '.NETFramework,Version=v4.6.1, .NETFramework,Version=v4.6.2, .NETFramework,Version=v4.7, .NETFramework,Version=v4.7.1, .NETFramework,Version=v4.7.2, .NETFramework,Version=v4.8, .NETFramework,Version=v4.8.1' instead of the project target framework 'net7.0'. This package may not be fully compatible with your project.

All of above projects (Core, Application, Infrastructure, Web) uses NET 7. What's wrong with my NuGet package? How can I fix it?

These are my current .csproj: Web.csproj

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

    <PropertyGroup>
        <TargetFramework>net7.0</TargetFramework>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
        <IsPackable>true</IsPackable>
        <Version>1.3.2</Version>
    </PropertyGroup>

    <ItemGroup>
        <ProjectReference Include="..\Application\Application.csproj" />
        <ProjectReference Include="..\Infrastructure\Infrastructure.csproj" />
    </ItemGroup>

</Project>

Application.csproj:

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

<PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.0" />
    <PackageReference Include="NSec.Cryptography" Version="22.4.0" />
    <PackageReference Include="Paseto.Core" Version="1.0.7" />
</ItemGroup>

<ItemGroup>
    <ProjectReference Include="..\Core\Core.csproj" />
</ItemGroup>

Infrastructure.csproj:

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

<PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.0" />
</ItemGroup>

<ItemGroup>
    <ProjectReference Include="..\Application\Application.csproj" />
</ItemGroup>

And the Core.csproj:

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

  <PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Base64-Url" Version="1.0.0" />
  </ItemGroup>

</Project>
Jesse
  • 3,243
  • 1
  • 22
  • 29
Szyszka947
  • 473
  • 2
  • 5
  • 21
  • I fixed it by setting up another version of my `Web..csproj`. It turns out that even the package is removed and a new one is added, if we do not change the version, the package will contain the old content. What's more, here occurs a new problem but closely linked. When I pack my `Web.csproj`, then nuget packages from other projects on which `Web.csproj` depends aren't included in my nuget package and my package is not working, because necessary nugets on which my nuget package depends aren't included. How to fix it? – Szyszka947 Nov 24 '22 at 16:07
  • Not sure what is `Web` project. If it's an executable asp.net or similar then you are doing nuget the wrong way. Nuget package should contain a **library** code, i.e. the reusable set of classes and methods. But if it's a library, then instead of using `-IncludeReferencedProjects` you can pack all these projects **separately** and give them all the same version number (not a must but this is the simplest way to may things work). Then installing the top-level `Web` project will install all the lower-level packages. And each of them will install their 3rd party dependencies. – Artur Nov 25 '22 at 15:01
  • Regarding your comment about versions: when you install the package version `X` it's downloaded into the [global cache folder](https://learn.microsoft.com/en-us/nuget/consume-packages/managing-the-global-packages-and-cache-folders). But when you remove the package the cache is not cleared. Then, when you are installing the package with version `X` again, nuget tool finds the cached version and uses it ignoring the newly created package. One solution is what you did: changing the version. The second solution is removing the cached version before reinstalling. – Artur Nov 25 '22 at 15:08
  • Yes, my Web is an asp net core app. I'm excluding the .exe by -Exclude parameter in nuget pack command. Hm, so you think that I should create a nuget package for every project in my solution, and there won't be any problem with packages. But what if I must do it with only one nuget package? Please refer to my question in bounty because I described better the problem there. – Szyszka947 Nov 25 '22 at 15:33
  • I'm sorry, but I'm not going to help you do that. Because it is the fundamentally wrong usage of nuget packages. You shouldn't share the business logic in packages, you should share the cross-cutting reusable code, like logging, authentication, different sort of helpers etc. – Artur Nov 25 '22 at 16:19
  • Why do you think that this is wrong? Do you think that e.g. IdentityServer package is also wrong, because it contains business logic, controllers etc (https://www.nuget.org/packages/IdentityServer4)? I'm writing really something like IdentityServer, so I cant give a implementer free hand for writing their own business logic, own controllers etc. because this causes security problems for the potential user. – Szyszka947 Nov 25 '22 at 16:31
  • I don't think it's a good approach to create a nuget that way, but to help you, could you send an example of the .csproj? Maybe this [link](https://stackoverflow.com/questions/40396161/include-all-dependencies-using-dotnet-pack) can help you – matmahnke Nov 29 '22 at 05:29
  • 1
    Look [inside](https://nuget.info/packages/IdentityServer4/4.1.2) the IS4 package and you will find a single assembly. Because it was built from a single project. And there is no business logic inside, there is a framework that can be used by anyone. – Artur Nov 29 '22 at 14:58
  • I added .csproj of all my projects to my question. Your solution is doing that all referenced projects are included in package. But the packages on that these projects depends are not. (like with -IncludeReferencedProjects) – Szyszka947 Nov 29 '22 at 16:08
  • I agree with @Artur -- making something both a web application and a NuGet package is fundamentally misusing NuGet. Why do you think `Web` needs a package? The fact that you are excluding an executable from a NuGet package is a red flag. – Kit Nov 29 '22 at 19:27
  • I have to use `Web.csproj` to provide necessary API controllers. I really don't understand what's wrong here. – Szyszka947 Nov 29 '22 at 19:38

1 Answers1

1

From what I've been researching, there isn't a good way to do this. However, it is possible to get around it in two different ways.

1. Edit the .csproj posted by teneko

Where you should edit the .csproj adding PrivateAssets="All” in all references and adding the <TargetsForTfmSpecificBuildOutput> into web.api followed by <target> block.

Web.csproj

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

    <PropertyGroup>
        <TargetFramework>net7.0</TargetFramework>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
        <IsPackable>true</IsPackable>
        <Version>1.3.2</Version>
    </PropertyGroup>

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

   <PropertyGroup>
        <TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage</TargetsForTfmSpecificBuildOutput>
    </PropertyGroup>
    <Target Name="CopyProjectReferencesToPackage" DependsOnTargets="BuildOnlySettings;ResolveReferences">
    <ItemGroup>
      <!-- Filter out unnecessary files -->
      <_ReferenceCopyLocalPaths Include="@(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference')->WithMetadataValue('PrivateAssets', 'All'))"/>
    </ItemGroup>

    <!-- Print batches for debug purposes -->
    <Message Text="Batch for .nupkg: ReferenceCopyLocalPaths = @(_ReferenceCopyLocalPaths), ReferenceCopyLocalPaths.DestinationSubDirectory = %(_ReferenceCopyLocalPaths.DestinationSubDirectory) Filename = %(_ReferenceCopyLocalPaths.Filename) Extension = %(_ReferenceCopyLocalPaths.Extension)" Importance="High" Condition="'@(_ReferenceCopyLocalPaths)' != ''" />

    <ItemGroup>
      <!-- Add file to package with consideration of sub folder. If empty, the root folder is chosen. -->
      <BuildOutputInPackage Include="@(_ReferenceCopyLocalPaths)" TargetPath="%(_ReferenceCopyLocalPaths.DestinationSubDirectory)"/>
    </ItemGroup>
  </Target>
</Project>

application.csproj

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

<PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.0" />
    <PackageReference Include="NSec.Cryptography" Version="22.4.0" />
    <PackageReference Include="Paseto.Core" Version="1.0.7" />
</ItemGroup>

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

infrastructure.csproj

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

<PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.0" />
</ItemGroup>

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

The core.csproj does not change.

And apply the command dotnet pack --output C:/MySampleApi -p:PackageVersion=1.3.2

This approach relies on you editing the .csproj, this can interfere with test references. I believe it's wrong to have to edit code that works to be able to provide it in a different way.

2. The nuspec approach

You will need create a nuspec file into your /project/web folder and add the <files> tag to add your dlls from /bin/release into C:/users/<username>/.nuget/packages/<project-name>/<version>/lib/net7.0 (if you are using windows), the nuspec file should seems like this.

web.nuspec (you can get an real example into C:/users/<username>/.nuget/packages/<project-name>/<existing-version>)

<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
  <metadata>
    <id>web</id>
    <version>10.0.6</version>
    <authors>api</authors>
    <description>Package Description</description>
    <dependencies>
      <group targetFramework="net7.0">
        <dependency id="Swashbuckle.AspNetCore" version="6.2.3" exclude="Build,Analyzers" />
      </group>
    </dependencies>
    <frameworkReferences>
      <group targetFramework="net7.0">
        <frameworkReference name="Microsoft.AspNetCore.App" />
      </group>
    </frameworkReferences>
    <contentFiles>
      <files include="any/net7.0/appsettings.Development.json" buildAction="Content" />
      <files include="any/net7.0/appsettings.json" buildAction="Content" />
    </contentFiles>
  </metadata>
   <files>
     <file src="bin\Release\net7.0\api.dll" target="lib\net7.0" />
     <file src="bin\Release\net7.0\core.dll" target="lib\net7.0" />
     <file src="bin\Release\net7.0\infrastructure.dll" target="lib\net7.0" />
     <file src="bin\Release\net7.0\application.dll" target="lib\net7.0" />
  </files>
</package>

note that the version is specified in this file and will no longer work in .csproj or by command line.

Run this command into project/web folder: dotnet pack --output C:/MyNugetSource -c Release -p:NuspecFile=web.nuspec

This approach doesn't mess up the code but relies on manipulating a configuration file and being careful with directory names. This can impact CI pipelines.

Finally, I believe that the best approach would be to create nuget packages for each project in the solution, in practice versioning the core, infrastructure and application and installing each one separately, it is worth evaluating the feasibility of this.

This answer was based on this post

Sorry for my english and i hope this helps.

matmahnke
  • 432
  • 3
  • 10