Converting an existing NuGet class library to support multiple frameworks

Converting an existing NuGet class library to support multiple frameworks

With the new .NET 5 platform, I wanted to find out how to convert an existing NuGet class library to support netstandard2.0 as well as net5.0.
This way I can use this package in multiple .NET projects which target .NET Framework 4.7.2, .NET Core 3 and .NET 5.

My example contains two projects which both use the older .NET version 4.0.
First project is My.MainProject which references project My.ModelProject

visual-studio-projects

Step one is to convert the projects to a new project format. You can do this by editing the .csproj file and replace it with something like this.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>
</Project>

In this example I targeted the framework to use netstandard2.0.

After reloading the project in Visual Studio I was missing a package reference.
Because of compability issues which may arise later I am referencing not the latest 5.0 version but the 4.4.0 version. The project file now looks like this:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="System.ComponentModel.Annotations" Version="4.4.0" />
  </ItemGroup>
</Project>

With the reference to the ModelProject, my MainProject.csproj now looks like this:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="System.Configuration.ConfigurationManager" Version="4.4.0" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\My.ModelProject\My.ModelProject.csproj" />
  </ItemGroup>
</Project>

The solution now builds successfully. The dependencies and references are in place.

Step two is to make the projects support multiple frameworks. You can try to do this in Visual Studio by going to the properties of the project, only to find out that you can select only one target framework.

visual-studio-target-framework

This means we have to manually edit the project file. We need to replace the <TargetFramework> tag with <TargetFrameworks>.
This way we can add multiple target frameworks because we also want .NET 5 support.
The tag now looks like this: <TargetFrameworks>netstandard2.0;net5.0</TargetFrameworks>.

After the changes to the project file are saved, Visual Studio will reload the projects and will tell you if anything is missing or needs attention. In my case everything loaded normally. When you go to ‘dependencies’ in your project you can see the different frameworks:

visual-studio-dependencies

Step 3 is to create a Nuget package. For this we will not use a .nuspec file but we will add the fields to the MainProject. You can enter this in the ‘package’ tab in the properties of your project.

nuget-properties

In our project file the data is added to the PropertyGroup tag.

  <PropertyGroup>
    <TargetFrameworks>netstandard2.0;net5.0</TargetFrameworks>
    <Company>Sentia</Company>
    <Authors>Ger Groot</Authors>
    <PackageLicenseFile>readme.md</PackageLicenseFile>
    <Description>Multi target example project.</Description>
    <Copyright>2021</Copyright>
  </PropertyGroup>

Now we can create a package by selecting ‘pack’ in the context menu of the project file.

right-click-pack

This will create a file My.MainProject.1.0.0.nupkg in folder bin/Debug. Because the .nupkg is actually a zipfile we can use Winrar to open it.
The contents of the file looks like this:

nuget-contents-single-project

In the lib folder you will find two folders; netstandard2.0 and net5.0.
The content of the folder however will only contain 1 file ‘My.MainProject.dll’.
What is missing here is the dll of our ModelProject.

nuget-only-one-dll

Step 4 we also need to have the My.ModelProject.dll in our NuGet package. Unfortunately I could not find out how to do this directly in Visual Studio by using the settings or property options.
After some searching on Google I found the following article on the Github page of NuGet.

It seems that the only way to do this is by editing the project file and adding some new stuff. After adding that, my project file now looks like this:

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

  <PropertyGroup>
    <TargetFrameworks>netstandard2.0;net5.0</TargetFrameworks>
    <Company>Sentia</Company>
    <Authors>Ger Groot</Authors>
    <PackageLicenseFile>readme.md</PackageLicenseFile>
    <Description>Multi target example project.</Description>
    <Copyright>2021</Copyright>
    <TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage</TargetsForTfmSpecificBuildOutput>
  </PropertyGroup>

  <Target Name="CopyProjectReferencesToPackage" DependsOnTargets="BuildOnlySettings;ResolveReferences">
    <ItemGroup>
      <BuildOutputInPackage Include="@(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference')->WithMetadataValue('PrivateAssets', 'All'))" />
    </ItemGroup>
  </Target>

  <ItemGroup>
    <ProjectReference Include="..\My.ModelProject\My.ModelProject.csproj" PrivateAssets="all" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="System.Configuration.ConfigurationManager" Version="4.4.0" />
  </ItemGroup>

  <ItemGroup>
    <None Include="readme.md">
      <Pack>True</Pack>
      <PackagePath></PackagePath>
    </None>
  </ItemGroup>

</Project>

Now after saving the project file I can try again to create the NuGet package by using the ‘pack’ option.
Now the 2 files are present. Mission accomplished!

nuget-two-dll

Step 5, bonus step.
Next I want this all to build in Azure Devops.
Before all this we used a .nuspec file to create the NuGet package. Because of the new project setup we no longer have to.
This however means that you need to change your Pipeline in Azure Devops.

In the old situation we used the task [email protected] to pack the file. This has to be changed to task [email protected]

old situation:

- task: [email protected]
  inputs:
    command: 'pack'
    packagesToPack: '**/My.MainProject.csproj'
    versioningScheme: 'byBuildNumber'
    includeSymbols: false

new situation:

- task: [email protected]
  displayName: 'pack'
  inputs:
    command: 'pack'
    packagesToPack: '**/My.MainProject.csproj'
    versioningScheme: 'byBuildNumber'

Sources:

Ger Groot
Ger Groot

DotNet developer