10 March 2017

Sharing libraries between .Net Core and .Net Framework applications

[Updated: Nov 2018] If you plan to make any significant commitment to .Net Core there is a fair chance that you’ll need to share some libraries with .Net framework applications. You will have some legacy baggage that cannot be migrated easily and .Net Core is an emerging ecosystem with a number of missing pieces.

If you want to evolve towards a mixed ecosystem you have two options for sharing libraries. Firstly, you can develop a .Net Standard library that can be shared directly between applications so long as the versions match up. Alternatively, you can use multi-targeting to cross-compile a library for more than one platform.

Sharing through the .Net Standard

Microsoft introduced the .Net Standard to provide a common standard for APIs across the Microsoft ecosystem. It can be seen as a successor to portable class libraries (PCLs) that simplifies the business of targeting different platforms. Where PCLs are based on profiles defined by intersecting platform capabilities, the .Net Standard provides a curated set of APIs.

The upshot is that you can create libraries that can be directly referenced by .Net Framework, .Net Core and Xamarin applications. You just need to ensure that the .Net Standard Library NuGet package is added to any application that wants to reference a .Net Standard library.

Getting to this stage has been a slow process. There were more than half a dozen versions of .Net Standard in the early days and it was not always immediately clear which version to target. It felt like instead of suffering DLL hell, we now had .Net Standard hell to contend with. The release of version 2.0 was released in August 2017 created a de facto standard and will be the last version that allows you to write code for both .Net Core and the .Net framework.

Most development shops have a mixture of .Net framework versions in production. This can make the adoption of .Net Standard libraries quite difficult, particularly in larger ecosystems. If you aren’t compiling against 4.6.1 then you won’t be able to share libraries with a usable version of .Net Core. The lowest currently supported version of the .Net Framework is 4.5.2, but you’d be surprised how much .Net 4.0 or even 3.5 is lurking in some dark corners.

Something else to bear in mind with .Net Standard is the number of extra DLLs you’ll need to ship with any application that uses it. Adding a .Net Standard 2.0 library to a v4.6.1 framework applications bloats the runtime by an extra 97 DLLs. That’s a lot of extra weight to add to any framework installation.

Multi-targeting

For messier ecosystems that cannot guarantee a more recent vintage of the .Net framework, multi-targeting can help to widen the reach of shared libraries. This allows you to compile a single project natively for both .Net Standard and your preferred version of the .Net Framework, though it does come at a cost of having to manage multiple sets of compiled output.

This has become a lot more straightforward since Visual Studio 2017 was released. Multi-targeting was a pretty choppy experience in Visual Studio 2015 using the old JSON-based xproj format. You could get a project to compile for more than one framework, but you were not able to use a direct project reference in a .Net Framework project. This required some pretty difficult workarounds: you either had to migrate the target project to the xproj structure or distribute the dependency using NuGet. Neither approach was ideal.

This has been addressed in the new tooling, though you do still have to manually edit the project files to get it working. Re-loading the solution is also recommended after making any manual changes to the frameworks.

The example below shows how you would adjust the TargetFramework element to get a project to compile for more than one framework:

<PropertyGroup>
  <TargetFrameworks>net452;netstandard1.3</TargetFrameworks>
</PropertyGroup>

When you compile this project you will see two outputs in the BIN folder, one for each of the frameworks specified. This allows you to create project references to the shared assembly, both for projects built with .Net Framework 4.5.2 or anything built on .Net Core 1.0.

Migrating existing libraries

When planning shared libraries bear in mind that the .Net Standard is based on a subset of the overall APIs currently available in the .Net Framework. Many of the APIs that you may have grown accustomed to using in the .Net Framework will not be part of the .Net Standard, particularly once you get into those areas that have been most heavily refactored by .Net Core (e.g. ASP.Net).

Implementing existing .Net Framework libraries as shared .Net Standard libraries inevitably requires a migration, similar to porting to .Net Core. Microsoft’s API Port tool can tell you how much work this will involve. It is a command line tool that gives you a detailed breakdown of the types and members that will cause compatibility issues. In some cases, they will require a particular version of the .Net Standard, while others will not be supported at all.

Once again, the greatest difficulties are likely to be around external dependencies. Multi-targeting will only work if all the dependencies in the project also support your target frameworks. Some implementation differences can be smoothed over with conditional compilation statements, but one of the main intentions of the .Net Standard is to eliminate the need for this kind of workaround.

Unification and the compatibility shim

Version 2.0 of .Net Standard finally provided a single set of APIs that could be applied across .Net Core 2.0, .Net Framework 4.6.1 and Xamarin. It also provided a compatibility shim that allows .Net Framework assemblies to be used directly by .Net Standard libraries.

The compatibility shim should come with a significant health warning. It allows framework libraries to be compiled into .Net Standard applications, but it doesn’t guarantee that they will work. It works by bridging the differences in assembly naming between the framework and .Net Standard. This doesn’t take account of missing types that are only picked up at runt-time.

Using the shim requires a certain leap of faith. If you add a .Net Framework assembly into a Core or Standard project, it will compile but with the dreaded “This package may not be fully compatible with your project” warning. You can only verify the availability of all the types by running the project, so you can only guarantee compatibility through run-time testing.

The stuff that will never be shared

Despite the growing compatibility promised by .Net Standard 2.0 there are still some areas that will never be part of the .Net Core world. Windows Forms and WPF in particular will forever remain in the .Net Framework as Windows Universal applications have become the new UI creed. Anything that relies on platform-specific features will also be excluded. For example, you cannot build a Windows Service using .Net Core and are expected to use a platform-specific process manager (e.g. NSSM for Windows or Supervisor for Linux) rather than a project template or library like TopShelf.

The fate of WCF is a little more ambiguous. There is still a team working on it, but any port to .Net Core seems to be forever on the backburner. This is pretty bad news for anybody who has made a significant investment in WCF for their communications infrastructure. This can create an architectural barrier to adoption which will be much trickier to negotiate than a bunch of .Net Framework dependencies.

Filed under .Net Core, ASP.NET, C#, Net Framework.