How to create a library compatible with .NET Core 3.x, 2.x and .NET Framework

Let’s assume that you have a large library with a lot of functionality and you want to use it in three different web projects. However you have a problem, the target framework for these projects is not the same. One of them is older and it’s currently using .NET Framework 4.8, the second is newer and it’s using .NET Core 2.2 and the last one is the newest using .NET Core 3.1. Upgrading the projects to .NET Core it’s not an option at the present time due to the large number of modifications required and of course time related constrains.

Choosing the library target framework

Let’s choose which target framework is best for us. According to https://dotnet.microsoft.com/platform/dotnet-standard .NET Standard 2.0 is the right choice because it’s compatible with the .NET Core 2.x, 3.x and .NET Framework > 4.6.1

Job done?

Incompatibilities between .NET Core 2.x and 3.x

You might think that if you have a NET Standard 2.0 library then you can use it with no problems in all target frameworks from the above image. I thought so too initially. However in reality making it work can be a droughting task.

Each web project has its packages. And your web project might have a package which targets a single target framework. Its sub packages will accept a version range which does not match your library packages. And for .NET 2.2 you have exactly that, a mandatory preinstalled package called Microsoft.AspNetCore.App:

Note that this was designed for .NET Core 2.2 only and that it’s requiring most of the existing Microsoft packages with versions between 2.2.0 and 2.3.0.

Let’s assume that our reusable library uses .NET Standard 2.0 as target framework and that it has 3 NuGet packages, 2 of them are latest Microsoft 3.x. This is the .csproj file:

Adding this library to a .NET Core 3.1 project will work just fine since .NET Standard 2.0 is fully compatible with it.

Adding it to our .NET Core 2.2 project will start to reveal the problems we are going be facing here (Error NU1107 Version conflict detected for … Install/reference … directly to project … to resolve this issue.):

Taking a look at the package dependencies for a .NET Core 2.2 project we see that the culprit is Microsoft.AspNetCore.App package, which has a lot of Microsoft dependencies with version 2.2.0 including Microsoft.Extensions.Configuration 2.2.0.

Our library uses Microsoft.Extensions.Http which internally uses the same Microsoft.Extensions.Configuration dependency but with version 3.1.9. And we have a version conflict.

Let’s try to reference Microsoft.Extensions.Configuration 3.1.9 directly to project to resolve this issue, like the error suggests (install/reference … directly to project … to resolve this issue). Doing so we get more details about what the real problem is:

Microsoft.AspNetCore.App 2.2.0 accepts a version range between (>= 2.2.0 && < 2.3.0) for Microsoft.Extensions.Configuration and we cannot use Microsoft.Extensions.Configuration 3.1.9 from our library.

Microsoft.Extensions.Configuration is used as an example but each Microsoft 3.x package you use is most likely going to have a conflict with a package from Microsoft.AspNetCore.App. Because of it no Microsoft 3.x package cannot be used in a .NET Core 2.2 web app.

Solution 1

The immediate fix is to use in our library only Microsoft NuGet packages with version 2.2.0 (Microsoft.Extensions.DependencyInjection.Abstractions and Microsoft.Extensions.Http 2.2.0).

But what will happen when this library is used in a .NET Core 3.1 project which also has Microsoft 3.x libraries installed?

It will work because a .NET Core 3.1 project will increase the references version from 2.x to 3.x wherever necessary. In the below image on top we have our library project in standalone and on bottom we have our library added in a .NET Core 3.1 project:

Notice how the project reference Microsoft.Extensions.Logging modifies the library references versions also.

This works in our case because according to https://www.nuget.org/packages/Microsoft.Extensions.Http/2.2.0 , Microsoft.Extensions.Http requires that its sub packages to have a final version ≥ 2.2.0.

This solution has some drawbacks.

  1. Microsoft.Extensions.Http is a happy case but we could have packages which restricts its sub packages to a version range. If Microsoft.Extensions.Http would require its sub packages to have a maximum version of 2.3.0 and if we are using the same sub package with version 3.x in a .NET Core 3.x app, then this solution will not work.
  2. You will have a mix of Microsoft 2.x and 3.x packages. Although technically they should work fine, in reality you might find incompatibilities at runtime.
  3. Another developer might think to upgrade Microsoft references from your library, and break the compatibility with .NET Core 2.x.

Solution 2 (the complete one)

The solution is to add multiple TargetFrameworks in our .csproj file. This can be done only by manually edited the .csproj file, it cannot be done from Visual Studio UI.

We could add netcoreapp3.1 in TargetFrameworks but let’s use .NET Standard 2.1 since it’s compatible with .NET Core 3.0 and other, offering a larger usability.

Note that .NET Standard 2.1 is not compatible with .NET Core 2.x. and this allows us to add conditional reference based on the current target framework. In our case we will add Microsoft references with version 2.x for .NET Standard 2.0 and 3.x for .NET Standard 2.1. The final library .csproj file will look like this:

You can see the effect in the library dependencies:

Adding the library in our web projects we can see how it knows to use the correct NuGet versions based on our conditions:

Incompatibilities between .NET Core and .NET Framework

We have a library which works in our .NET Core 2.x and 3.x. What about the third .NET Framework web project?

Although .NET Framework is compatible with .NET Standard 2.0 and with all package references from our example library, it has two major differences from a .NET Core project that will affect us:

  1. The restore project style
  2. The Dependency Injection framework

Restore Project Style

When you try to access a piece a code found in the reference of a reference then you must install the second reference in your .NET Framework project.

For eg. our example library uses Microsoft.Extensions.DependencyInjection so you would think that referencing the library in a .NET Framework project will allow you to use Microsoft.Extensions.DependencyInjection in your code. But this is not the case with .NET Framework. Surely you can manually add all the packages you need but it will take a lot of work and maintenance.

The solution is to add a different RestoreProjectStyle to PropertyGroup tag from your .NET Framework .csproj file. This will allow to restore all sub packages like a .NET Standard project:

Dependency injection framework

Our example library uses Microsoft.Extensions.DependencyInjection which is the way to go but most .NET Frameworks use Autofac or other dependency injection frameworks. Rewriting the project to use Microsoft.Extensions.DependencyInjection is a difficult and time consuming task. Good news is that we can combine the two:

  1. Install in your project the package Autofac.Extensions.DependencyInjection and Microsoft.Extensions.DependencyInjection if not already added by a reference project.

2. Create a ServiceCollection in Startup.cs or Global.asax.cs since we will need it.

IServiceCollection services = new ServiceCollection();

3. Call library functionality or others Microsoft.Extensions.DependencyInjection declarations:

services.AddCountriesApi();

4. Populate Autofac container with IServiceCollection services:

builder.Populate(services);

In other words we can define services using Microsoft.Extensions.DependencyInjection, then an Autofac container can absorb them and in the end at runtime just Autofac will be used. Convenient!

In the end you should have something like this:

Working example

I’ve put together a working example which can be found here:

The library offers support for a HTTP client used for getting the countries from https://restcountries.eu/ public API based on a name filter.

The solution contains:

  1. CompatibleLibrary. The library exposes extension method void AddCountriesApi(this IServiceCollection serviceCollection) which allows injecting ICountriesServiceClient interface. The interface has GetCountries method which allows to get a list of countries details based on a name search.
  2. WebApplicationNetCore2 (.NET Core 3.1 web project)
  3. WebApplicationNetCore2 (.NET Core 2.2 web project)
  4. WebApplicationNetFram48 (.NET Framework 4.8 web project)

All web projects contains CountriesController API controller where ICountriesServiceClient is injected and used. Feel free to start each web project separately and to notice the behavior.

Conclusion

Reusable code is one of the most important things in programing and since most companies have common logic in different projects it’s good that you can create libraries or modify existing ones to have a wide range of compatibility.

Package version conflict is one of the errors which are very hard to fix especially if you are facing this issue for the first time.

Hopefully this page offers you insight of how to apply the above in your work. Any comments or improvements are much appreciated.

Full Stack Senior Software Developer

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store