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.
Given that there have been more than half a dozen versions of the .Net Standard already it’s has not always immediately clear which version to target. It can feel like instead of suffering DLL hell, we now have .Net Standard hell. In general, the higher the version of .Net standard then the wider range of APIs that will be available to you. The lower the version the wider range of platforms you’ll be able to support.
As ever with any .Net Core migration, you are the prisoner of your dependencies. Version 2.0 has become the de facto standard since it was released and it has been widely adopted by commonly-used libraries. the support for anything below version 1.4 is pretty sketchy. In practical terms, this can tie you into a minimum .Net Framework version of 4.6.1.
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. You’d be surprised how much .Net 4.0 or even 3.5 is lurking in some dark corners. Even version 4.5.2 may still be supported by Microsoft, but it is limited to version 1.2 of the .Net Standard.
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.
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.