Tag Archives: .NET Standard

.NET Core 2.0: Referencing .NET Framework Libraries: A Topshelf Experiment

Referencing .NET Framework Libraries in .NET Core 1.1

There are many old libraries targeting the .NET Framework which, for various reasons, do not yet target .NET Core or .NET Standard. Topshelf, a fantastic library that helps you to easily create Windows services, is one of these. At the time of writing this article, the last release of Topshelf was 4.0.3 back in October 2016. There was no way that Topshelf could target .NET Standard because the .NET Standard spec did not support the APIs that are required for it to function.

This was a problem because there was no way you could create a Windows service using Topshelf for a .NET Core 1.1 application. Simply trying:

Install-Package Topshelf

…is bound to fail miserably:

If you want to make a Windows service out of an application targeting .NET Core 1.1, then you have to use an alternative such as NSSM.

What Changed in .NET Core 2.0

With the release of .NET Core 2.0 and .NET Standard 2.0, applications or libraries targeting either of these are able to reference old libraries targeting the full .NET Framework. Presumably this is because the .NET Core/Standard 2.0 implementations have enough API coverage to overlap with what the full framework was able to offer.

Quoting the .NET Core/Standard 2.0 announcement linked above:

“You can now reference .NET Framework libraries from .NET Standard libraries using Visual Studio 2017 15.3. This feature helps you migrate .NET Framework code to .NET Standard or .NET Core over time (start with binaries and then move to source). It is also useful in the case that the source code is no longer accessible or is lost for a .NET Framework library, enabling it to be still be used in new scenarios.

“We expect that this feature will be used most commonly from .NET Standard libraries. It also works for .NET Core apps and libraries. They can depend on .NET Framework libraries, too.

“The supported scenario is referencing a .NET Framework library that happens to only use types within the .NET Standard API set. Also, it is only supported for libraries that target .NET Framework 4.6.1 or earlier (even .NET Framework 1.0 is fine). If the .NET Framework library you reference relies on WPF, the library will not work (or at least not in all cases). You can use libraries that depend on additional APIs,but not for the codepaths you use. In that case, you will need to invest singificantly in testing.”

Example with Topshelf

In order to actually test this out, you’ll need to have Visual Studio 15.3 or later. You will also need to separately install the .NET Core 2.0 SDK.

In an earlier section, we tried installing Topshelf in a .NET Core 1.1 application, and failed. Let’s try doing the same thing with a .NET Core 2.0 application:

Install-Package Topshelf

The package installation works pretty well:

However, the warning that shows under the dependency is not very promising:

There’s only one way to find out whether this will actually work in practice.

Let’s steal the code from the Topshelf quickstart documentation:

public class TownCrier
{
    readonly Timer _timer;
    public TownCrier()
    {
        _timer = new Timer(1000) {AutoReset = true};
        _timer.Elapsed += (sender, eventArgs) => Console.WriteLine("It is {0} and all is well", DateTime.Now);
    }
    public void Start() { _timer.Start(); }
    public void Stop() { _timer.Stop(); }
}

public class Program
{
    public static void Main()
    {
        HostFactory.Run(x =>                                 //1
        {
            x.Service<TownCrier>(s =>                        //2
            {
               s.ConstructUsing(name=> new TownCrier());     //3
               s.WhenStarted(tc => tc.Start());              //4
               s.WhenStopped(tc => tc.Stop());               //5
            });
            x.RunAsLocalSystem();                            //6

            x.SetDescription("Sample Topshelf Host");        //7
            x.SetDisplayName("Stuff");                       //8
            x.SetServiceName("Stuff");                       //9
        });                                                  //10
    }
}

Nope, looks like Topshelf won’t work even now.

I guess the APIs supported by .NET Core 2.0 still do not have enough functionality for Topshelf work as-is. Other .NET Framework libraries may work though, depending on the dependencies they require. In the “.NET Core 2.0 Released!” video, one of the demos shows SharpZipLib 0.86 (last released in 2011) being installed in an ASP .NET Core 2.0 application. It is shown to build, but we don’t get to see whether it works at runtime.

It is still early, and I suppose we have yet to learn more about the full extent of support for .NET Framework libraries from .NET Core 2.0 applications and .NET Standard 2.0 libraries. The problem is that when evaluating a third-party library such as Topshelf, it’s difficult to determine whether its own dependencies fall within the .NET Standard API set. This looks to me like a matter of pure trial and error.

Multi-Targeting .NET Standard Class Libraries

The .NET family has grown quite a bit, and to be honest, it’s a bit of a confusing mess. As I already explained in “Migrating Dandago.Finance to .NET Core“, there are now several different types of class library you can choose from (including different kinds of portable class libraries), different ideas of cross-platform (.NET Core vs Universal Windows Platform), different frameworks, and .NET Standard.

Let’s consider the following image, which shows three different .NET-based frameworks in relation to .NET Standard:

Image credit: .NET Core, .NET Framework, Xamarin – The “WHAT and WHEN to use it”

Your applications will typically be built for one of the frameworks on top (e.g. .NET Framework). But when you create a class library, you can choose to have it target a specific framework, or .NET Standard. Just as a quick recap from “Migrating Dandago.Finance to .NET Core“, targeting a particular framework (e.g. .NET Core) will not let you use the class library on others (e.g. .NET Framework), but targeting .NET Standard keeps it compatible with all of them.

This means that class libraries that target .NET Standard have maximum compatibility, but there’s a cost: not all APIs are available for .NET Standard, and targeting .NET Standard is only compatible with certain recent versions of those frameworks. If you take a look at the compatibility chart, you’ll notice for instance that .NET Standard 1.3  supports .NET Framework 4.6 and onwards.

For the most part, this is okay. But sometimes, you may want to combine .NET Standard compatibility with specific features in a particular framework, or perhaps target .NET Standard but still support older versions of a framework because you have some legacy code you can’t upgrade yet.

Multi-Targeting

When you create a new .NET Standard Class Library, its .csproj file will look something like this:

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

  <PropertyGroup>
    <TargetFramework>netstandard1.4</TargetFramework>
  </PropertyGroup>

</Project>

It is now actually possible to target multiple frameworks, by changing the <TargetFramework> element to <TargetFrameworks> (just add an ‘s’) and putting in different target framework monikers separated by semicolons. For instance, the .csproj file for my .NET Settings Framework looks like this:

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

  <PropertyGroup>
    <TargetFrameworks>netstandard1.2;net452</TargetFrameworks>
    <!-- ... -->
  </PropertyGroup>

  <ItemGroup Condition="'$(TargetFramework)' == 'net452'">
    <Reference Include="System.Configuration" />
  </ItemGroup>

  <ItemGroup Condition="'$(TargetFramework)' == 'netstandard1.2'">
    <PackageReference Include="System.ComponentModel.TypeConverter" Version="4.3.0" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Configuration" Version="1.1.1" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="1.1.1" />
  </ItemGroup>

</Project>

In this project, I’m targeting .NET Standard to provide as much compatibility as possible, but I’m also using framework-specific assets from the .NET Framework (namely System.Configuration, which is used to work with App.config/Web.config files). Thus, I am targeting both .NET Standard 1.2+ and .NET 4.5.2+. This has the effect of creating separate builds for each framework.

I have dependencies in there that are applicable for both targets (the section at the end with no conditions), but I also have specific dependencies needed for each framework. For instance, the functionality that needs System.Configuration will only be available for the build that targets the full .NET Framework, and will not be usable in the .NET Standard build. Such functionality will have to be written within preprocessor directives to prevent them from breaking the other builds:

#if NET452

using System.Configuration;

// ...

#endif

Unfortunately, as of now, you have to edit the .csproj by hand if you want to do multi-targeting, because the Visual Studio tooling hasn’t quite caught up with it yet. In fact, the project settings will only allow you to target a specific version of .NET Standard from a dropdown:

And after you’ve edited the .csproj by hand, it won’t let you change it from the GUI:

Summary

  • Targeting .NET Core lets you go cross platform, but you can’t use that functionality with other frameworks.
  • Targeting .NET Standard is compatible with any framework that supports that version of .NET Standard.
  • You can multi-target a .NET Standard library to include framework-specific functionality (guarded with preprocessor directives) while keeping the rest of the library compatible with all the relevant frameworks.