.NET Standard and Multi-targeting

Corrado Cavalli
Jan 14 · 6 min read

Wanna run some platform specific code inside a .NET Standard library?

I love writing code, but I don’t like write repetitive and duplicated code so, as soon as code lines starts to increase, I immediately begin thinking about encapsulating them into a method for better readability and further reuse even if many times that code ends up being used just once.
I like the idea of less code=less problems=less maintenance=more happiness so as I am happy when I’m able to delete some unnecessary lines of code, I even more happy when the code I am writing can be encapsulated in its own library and maybe reused multiple times.
So imagine how happy I was when several years ago i first read about the Portable Class libraries (PCL)announcement: finally the same code can be reused on different kinds of project, amazing! and how I was even more happy when later the PCL concept has been extended to include Xamarin (so read it as Android and iOS) and now my code can now also run even on a different platform, wow!

Of course there were some limitations, the famous dialog window restricting the available API surface depending on selected platforms was always a bit hard to fight, but it was still an amazing piece of technology.

PCL platform selection

With the advent of .NET Core we got a brand new type of library: the .Net Standard library, nearly all cross platform APIs available, no more profile intersection limitation and code running unmodified from a Windows server to a Tizen based OS TV, looks like a developer dream come true (more on this here)

Unfortunately sometimes, especially when you are refactoring some old code into a .NET Standard library, it inevitably happens that you struggle with the need to include some ‘platform specific’ code into the .Net Standard library and various alternatives like dependency injection, compile constants, partials do not fit best in your specific scenario.
If code is Windows only related, the Windows Compatibility Pack for .NET Core can be a viable solution, but in most cases you need to be more platform agnostic and so you need a more generic solution
In the end what you need would be to have a cross-platform library where you can specify “when you are running on this platform, please run this specific code instead of the default one”

Sounds magic, and luckily is also possible, here’s how…

Welcome the TargetFrameworkS option

If you edit the .csproj file of a .NET Standad library (right click the project and select Edit xxx.csproj (finally no need to unload the project in Visual Studio) you will see something like this

<Project Sdk=”Microsoft.NET.Sdk”><PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>

</Project>

This indicates that the library targets .NET Standard 2.0 only.
Something not widely known is that by just modifying the TargetFramework tag we can made the same library compatible with more than one framework.
Here’s how to target both .NET Standard 2.0 and .NET Framework 4.7

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

Please note the added ‘s’ at the end of TargetFramework tag, is what makes the difference, and here’s how the project looks like inside Visual Studio

What happens under the hood is that the library get now compiled for each listed platform, you can easily discover this by looking at the bin folder of your project.

and we could now write something like this

public class MyViewModel
{
public MyViewModel()
{
#if NETSTANDARD2_0
//NetStandard 2.0 code
#else
//.NET Fx only code
#endif
}
}

Note how Visual Studio now also let us switch between platforms

and what makes this even more cool is that all this “code switching” is totally transparent to the users of the library, they don’t know whether they are running platform specific or ‘general’ code, they just reference the library and get the work done.

There’s just one ‘little problem’ with this: The platforms natively supported by TargetFrameworks at the moment of this writing is somewhat limited, to say, no iOS, Android, Tizen etc.
Luckily Oren Novotny a well known MVP and RD found a way to make it working by creating an extensions that add extra properties to MSBuild.

The package is MSBuild.Sdk.Extras and here’s how to use it.

  1. Create a global.json file with following content and add it at the root of your solution (1.6.65 is latest version at this time)
{
“msbuild-sdks”: {
“MSBuild.Sdk.Extras”: “1.6.65”
}
}

2) Update the .csproj of the library by changing the Sdk attribute and including the desired platforms

<Project Sdk=”MSBuild.Sdk.Extras”>
<PropertyGroup>
<TargetFrameworks>netstandard2.0;Xamarin.iOS10;MonoAndroid90;uap10.0.17763</TargetFrameworks>
</PropertyGroup>
</Project>

and that’s it, if you compile the project you will now see that in the bin output you will have all listed platforms

We are now ready to write code specific platform and we can do it in several ways, we can introduce some platform specific constants and diverge flow using #ifdef directive or, my favorite, using partial classes and implementing code specific code in his own class.
To introduce platform specific directives we can tweak the .csproj this way

<Project Sdk=”MSBuild.Sdk.Extras”>
<PropertyGroup>
<TargetFrameworks>netstandard2.0;Xamarin.iOS10;MonoAndroid90;uap10.0.17763</TargetFrameworks>
</PropertyGroup>

<! — Constants →
<PropertyGroup Condition=” $(TargetFramework.StartsWith(‘MonoAndroid’)) “>
<DefineConstants>ANDROID</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=” $(TargetFramework.StartsWith(‘Xamarin.iOS’)) “>
<DefineConstants>IOS</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=” $(TargetFramework.StartsWith(‘uap’)) “>
<DefineConstants>UWP</DefineConstants>
</PropertyGroup>
</Project>

We now have the opportunity to write code like this in our cross platform library

#if UWP
//...
#elif ANDROID
//...
#elif IOS
//...
#endif

A better architecture

While #ifdef solution works, i suggest it only for small tweaks, a better approach in my opinion is to segregate platform specific code in its own partial class this way.
Let’s tweak the .csproj again in order to have a Platform folder containing the various platform specific folders (UWP, Android, iOS…) and let’s load into them the platform specific file depending on selected platform so that only related code will be compiled into final package.
Here’s the complete .csproj

<Project Sdk="MSBuild.Sdk.Extras">

<PropertyGroup>
<TargetFrameworks>netstandard2.0;Xamarin.iOS10;MonoAndroid90;uap10.0.17763</TargetFrameworks>
</PropertyGroup>

<!--Includes-->
<ItemGroup>
<Compile Remove="Platforms\**\*.cs" />
<None Include="Platforms\**\*.cs" />
</ItemGroup>

<ItemGroup Condition=" $(TargetFramework.StartsWith('uap')) ">
<Compile Incude="Platforms\Uap\**\*.cs" />
</ItemGroup>

<ItemGroup Condition=" $(TargetFramework.StartsWith('Xamarin.iOS')) ">
<Compile Include="Platforms\Ios\**\*.cs" />
</ItemGroup>

<ItemGroup Condition=" $(TargetFramework.StartsWith('MonoAndroid')) ">
<Compile Include="Platforms\Android\**\*.cs" />
</ItemGroup>

<!--Constants-->
<PropertyGroup Condition=" $(TargetFramework.StartsWith('MonoAndroid')) ">
<DefineConstants>ANDROID</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition=" $(TargetFramework.StartsWith('Xamarin.iOS')) ">
<DefineConstants>IOS</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition=" $(TargetFramework.StartsWith('uap')) ">
<DefineConstants>UWP</DefineConstants>
</PropertyGroup>


</Project>

and here is how it looks like inside Solution Explorer

Platforms file layout

And here is how the MViewModel shared class can be organized

namespace MultiTargetingDemo.ViewModels
{
public partial class MyViewModel
{

public void CommonMethod()
{
//Common code goes here
}

}
}

and this is an example of the platform specific part class

namespace MultiTargetingDemo.ViewModels
{
public partial class MyViewModel
{
public void PlatformSpecificMethod()
{
//Platform code goes here
}
}
}

Note: Ignore Visual Studio’s “Member with the same signature already declared” warning.
This way the shared and the platform specific code is much better organized and isolated.

Conclusion

It takes a bit to make it working, especially because tooling isn’t perfect yet.I suggest you to close and reopen Visual Studio in case of troubles.
But once mastered this is a real weapon in your hands.

References

Multi Targeting video on YouTube
Xamarin University video
Merge conflict podcast
MSBuild Sdk Extras on GitHub

Corrado Cavalli

Online thoughts of a technology funatic

Corrado Cavalli

Written by

Microsoft MVP mad about technology. MTB & Ski mountaineering addicted.

Corrado Cavalli

Online thoughts of a technology funatic