Creating .NET Library that targets multiple frameworks

Intro

In this post, I will cover how easy it is to create a .NET Standard Class Library that allows you to target multiple .NET Framework versions (e.g. NET Standard, .NET Framework 4.7, 3.5, etc.). This allows you to write code in a way that it's backward compatible to your existing code base as well as newer using latest framework version.

One of the reasons why you should do this is if you're working against an existing code base that either cannot be updated to the latest framework (yet) or you are working with multiple teams that may or may not have their project(s) up-to-date. Regardless, by introducing a library that allows you to write once and target multiple frameworks is a no-brainer to me.

Library Implementation

First, let's create a new .NET Standard Class Library solution. This will be a code repository that is compatible for both .NET Standard and .NET Framework 4.7.

Create new .NET Standard Library solution

Once the solution is created, go ahead right click on the project and edit the .csproj file. If this is the first time you're working with .NET Standard library, you will notice right away how clean it is to work with. Let's make this project compatible with different versions of .NET Framework(s). You will notice

Before:

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

After:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>netstandard2.0;net461</TargetFrameworks>
  </PropertyGroup>
</Project>

Handling Dependencies

As you began to write additional code to this library, you will come to a point where you will realize that you need to handle dependencies based on the type of Framework you're targeting (e.g. .NET Standard, Core, 4.6, 3.5, etc.). And these dependencies will need to be loaded separately based on the Framework that you're either working on or consuming from. Below is a snippet of configuration code that you will have to use to tell our project file (or more specifically NuGet Package Manager) to load our dependencies based on the Framework.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>netstandard2.0;net461</TargetFrameworks>
  </PropertyGroup>

  <ItemGroup Condition=" '$(TargetFramework)' == 'net461' ">
      <!--.NET Framework 4.6.1 related dependencies go here -->
    <PackageReference Include="Unity" Version="4.0.1" />
  </ItemGroup>

  <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
      <!--.NET Standard related dependencies go here -->
    <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.1.1" />
  </ItemGroup>

  <ItemGroup>
    <!--Common dependencies go here-->
  </ItemGroup>
</Project>

Writing Framework Specific Code

Now, it's time to define framework specific code. Presuming that we're writing a library, we would want to encapsulate some of the plumbing work (e.g. Dependency Injection) when this library is consumed by someone else. And because we want to make it compatible for both .NET Standard as well as .NET Framework, we have to make sure that we expose method(s) that suits both.

The .NET Standard library uses IServiceCollection interface to register dependencies whereas .NET Framework uses IUnityContainer library. In order to support both of these implementation, we'd write something like below:

#if NETSTANDARD2_0
using Microsoft.Extensions.DependencyInjection;
#else
using Microsoft.Practices.Unity;
#endif

#region Namespaces

using My.Library;
#endregion

namespace CSF.Central.API.Client
{
    public static class DependencyConfigurator
    {
#if NETSTANDARD2_0
        public static IServiceCollection Configure(IServiceCollection services, ClientSetting clientSetting)
        {
            services.AddSingleton<IClientSetting>(clientSetting);
            services.AddTransient<ICloud, Cloud>();
            return services;
        }
#else        
        public static IUnityContainer Configure(IUnityContainer container, ClientSetting clientSetting)
        {
            container.RegisterInstance<IClientSetting>(clientSetting);
            container.RegisterType<ICloud, Cloud>();
            return container;
        }
#endif
    }
}

In the above code sample, you will notice that we have code segments separated out based on the Framework we're targeting. You can easily switch context between the two without having your IDE throw an error at you (see below).

Context Switching of Frameworks in Visual Studio

This allows you to not only visualize the code based on the target framework but also get feedback from the compiler about the compatibility of the code in respect to the Framework selected.