Multi-Targeting NuGet Packages

I am updating some NuGet packages to support .NET 10 and had to refresh my memory on how to multi-target NuGet packages that support multiple target frameworks. In my case, I am adding support for .NET 9 and .NET 10, but these steps are applicable to other .NET versions. I just decided to write it down in this article, so I can easily pull it up next time I need it.

1. Multi-target your project

In your .csproj:

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

  <PropertyGroup>
    <TargetFrameworks>net9.0;net10.0</TargetFrameworks>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

Ensure you use <TargetFrameworks> in your csproj file, and don’t try to add multiple versions to <TargetFramework> (this is the default in your csproj if you only support one .NET when you ran them library project template). Trust me, I’ve hit this multiple times…

Then build and pack your project:

dotnet pack

either from the command line or in Visual Studio.

The build will produce the following with the NuGet package (nupkg file):

lib/
  net9.0/
    YourLib.dll
  net10.0/
    YourLib.dll

Developers will then consume the version that is appropriate for the project version that they included your package in. This is handled by the NuGet package manager and the appropriate target framework version.

2. Handling API differences (when needed)

If your code needs to diverge between .NET 9 and 10, use the target framework constants:

#if NET10_0
    // .NET 10-only API
#else
    // .NET 9 fallback
#endif

3. Using new .NET 10 features safely

With the #if for framework version shown above, we can now use new .NET 10 features when it makes sense, but still support older versions of .NET. There are a couple of options for doing that…

Option A: Put .NET 10-only code behind guards

public static class ExceptionExtensions
{
    #if NET10_0
    extension(SomeType)
    {
        public static void NewExtension() { }
    }
    #else
    public static void ExtensionMethod(this Exception ex) { }
    #endif
}

Option B: Split files per Target Version

In this option rather than combining all code into one file, you can split it between different source files for each version.

MyFeature.net10.cs
MyFeature.net9.cs

And then guard on version within the full file:

#if NET10_0
public class Foo
{
}
#endif

Option B is better when there are wholesale changes in the source file and they are very different between versions. This keeps the changes isolated, you don’t have to scatter a lot of #if statements throughout your file.

If you only need one or two small changes in a source file to support a newer .NET version, then option A is probably the best choice.

4. Package references that vary by version

If you require different dependencies for the different .NET versions, then you can add conditional package references to your csproj file:

<ItemGroup Condition="'$(TargetFramework)' == 'net10.0'">
  <PackageReference Include="Some.Net10.Only.Package" Version="1.0.0" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
  <PackageReference Include="Some.Legacy.Package" Version="2.5.0" />
</ItemGroup>

NuGet will pick the right package version to use in each option.

Note: if you are using packages that target both .NET 9 and .NET 10 (like most of the NuGet packages for .NET itself), then you don’t need this type of conditional references. NuGet will already handle using the appropriate version on its own.

5. Verify your package before publishing

Always inspect that the output contains both versions of your library by unzipping the nukpg file produced by the build (in the bin\Debug folder):

unzip YourLib.1.0.0.nupkg

You should see the following structure within the nupkg:

  • lib/net9.0
  • lib/net10.0

with your package assemblies within each folder (targeting different versions).

6. Versioning guidance

You do not need separate NuGet package versions for .NET 9 vs 10. Both .NET targets are in a single NuGet package. So there is only one, single version for both .NET targets. If you are adding breaking changes then you should reversion the new package appropriately. And if you remove support for an older .NET version (like .NET 7-8), then you should change your package’s major version number because that would be considered a breaking change.

Conclusion

Those are the steps that you need to undertake to ensure that your NuGet package supports multiple .NET versions. It’s pretty straightforward to build this into your library and keeps your developers happy while they are also transitioning to new versions of .NET.

Leave a comment