Multi-targetting .Net Framework and .Net Core in a single project

’s easy!

  1. Create a .Net Core project, whether a netstandard library, or a netcore app.
  2. Open the project and replace the line:
    <TargetFramework>netcoreapp2.0</TargetFramework>

    with

    <TargetFrameworks>netcoreapp2.0;net45</TargetFrameworks>

And you’re done!

Okay only kidding. Nothing is that easy. Unless … if you project has almost no dependencies, and if you don’t need to support older .Net framework versions then this really is enough. There is a massive and impressive compatibility between .Net framework 4.x and .Net Core, especially for values of x ≥ 4.6.1.

So let’s move on to the hard bits, which are

  • Dealing with Dependencies.
  • Dealing with things that make your code fail to compile. Most obviously:
    • language features or framework calls such as async/await which were only supported in the C# compilers that came out with .Net framework 4.5 and later.
    • framework classes and methods not available in your target. For instance, HostingEnvironment.QueueBackgroundWorkItem() is only available from net452; or System.Net.HttpClient which first came out in net45.

Dealing with Dependencies

So, you tried steps 1 and 2 above and it didn’t work either because:
– Your netcore project references a nuget package—or a specific version–not available for your target netframework
– Your netcore project using parts of the framework that for a netframework target, require an explicit reference to a framework.

In this case, we take a longer route.
2.1. Get the netcore project working.

2.2. Create a new, temporary, netframework project and copy all your C# code into it. Visual Studio has good drag-n-drop support for dragging files from an Explorer window into a project.

2.3. Add whatever dependencies are needed to make the netframework project build, so that shortly we can inspect the project and find out what PackageReferences we will need.

  • You will find that for some NuGet dependencies, you wind up referencing quite different packages for core vs framework.
  • During this stage, you may end up compromising on which netframework version you want to target it. Targetting Net472 or later is easiest; Net462 or later is still pretty easy because it implements the whole of netstandard2. This gives you a good chance that code builds on both platforms without any #if #endif conditional compilation or any code-rewrite being needed. That said, the code rewrite needed to target net45 or even net40 might be minimal. So it may be worth the extra work to do that.
  • Changing between netframework versions may change the nuget packages you depend, so don’t bother looking at your final dependencies until they are … final.

2.4. Deal with any code that won’t compile under both netcore and netframework, usually by using conditional compilation. This part is the most work and may involve trade-offs. See https://docs.microsoft.com/en-us/dotnet/core/tutorials/libraries#how-to-multitarget for the constants to use in conditional sections.

2.5. Examine each of the csproj files, and carefully and conditionally copy the dependencies from the netframework `csproj` into a new PropertyGroup section in the netcore csproj.

Let’s look at “Conditional PropertyGroups in the netcore `csproj`” in more detail.

Conditional PropertyGroups in the netcore `csproj`

Here’s a typically `csproj` file for a small project:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
    <IsPackable>false</IsPackable>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.8.0" />
    <PackageReference Include="xunit" Version="2.4.0" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\PredicateDictionary\PredicateDictionary.csproj" />
    <ProjectReference Include="..\TestBase\TestBase.csproj" />
  </ItemGroup>
</Project>

You can see this is a test project. Having easily made my PredicateDictionary project – which has no dependencies – multitarget, I find that getting the Test project to multitarget is more work because of dependencies on the Test infrastructure.

The first thing to do is make these dependencies specific to your netcore target. Add a Condition attribute to the `` element holding the package references:

<ItemGroup Condition="$(TargetFramework.StartsWith('netcore') )" > 
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.8.0" />
    <PackageReference Include="xunit" Version="2.4.0" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
  </ItemGroup>

If you add more references to a project after multi-targetting you will almost certainly have to manually re-edit the `csproj` file to get the new references into the right conditional block. The MSBuild file format is very flexible. You can add, combine and reorder ItemGroups and PropertyGroups as you wish.

Now, look at your netframework `csproj` and look at the packages.config. If you see a long list of System.* packages, let’s hope for the moment we can ignore them and go straight to the real dependencies. I got these:

  <package id="xunit" version="2.4.1" targetFramework="net45" />
  <package id="xunit.abstractions" version="2.0.3" targetFramework="net45" />
  <package id="xunit.analyzers" version="0.10.0" targetFramework="net45" />
  <package id="xunit.assert" version="2.4.1" targetFramework="net45" />
  <package id="xunit.core" version="2.4.1" targetFramework="net45" />
  <package id="xunit.extensibility.core" version="2.4.1" targetFramework="net45" />
  <package id="xunit.extensibility.execution" version="2.4.1" targetFramework="net45" />

If you’re really lucky, your netframework dependencies will be identical to your netcore dependencies. But if it were that simple you’d probably have finished at section 1 above. So our first attempt will be to
– Add a New ItemGroup for the NetFramework references, with a matching Condition attribute.
– Add a PackageReference for each package in packages.config.
– Recall that XML is case-sensitive and the Version attribute in your netcore `csproj` file is Titlecased, unlike in the packages.config file.
– Notice that the `csproj` file format doesn’t use the targetFramework atttribute.

<ItemGroup Condition="$(TargetFramework.StartsWith('net4'))" >
  <PackageReference Include="xunit" Version="2.4.1" />
  <PackageReference Include="xunit.abstractions" Version="2.0.3" />
  <PackageReference Include="xunit.analyzers" Version="0.10.0" />
  <PackageReference Include="xunit.assert" Version="2.4.1" />
  <PackageReference Include="xunit.core" Version="2.4.1" />
  <PackageReference Include="xunit.extensibility.core" Version="2.4.1" />
  <PackageReference Include="xunit.extensibility.execution" Version="2.4.1" />
</ItemGroup>

Now I try to recompile. Since I haven’t yet changed the , I still only get NetCore compilation and nothing should have changed, but at least this confirms I haven’t broken the `csproj` file. Yet.

If the `csproj` doesn’t reload after editing, you’ve probably made a typo in the XML syntax. Redo your edits a line at a time until you find the typo. For instance, you might miss a “/>” self-close marker when tidying up the tags to single line.

Now, for the drumroll, we change

<TargetFramework>netcoreapp2.0</TargetFramework>

to

<TargetFrameworks>netcoreapp2.0;net45</TargetFrameworks>

–noticing of course that TargetFrameworks is now in the plural–and build and …

If you’re working on Linux or Mac, you’ll get an immediate fail. You will fix this by
Install Mono or by installing Visual Studio for Mac.
– Paste this section into your `csproj` file:

<PropertyGroup>
  <!-- When compiling .NET SDK 2.0 projects targeting .NET 4.x on Mono using 'dotnet build' you -->
  <!-- have to teach MSBuild where the Mono copy of the reference asssemblies is -->
  <TargetIsMono Condition="$(TargetFramework.StartsWith('net4')) and '$(OS)' == 'Unix'">true</TargetIsMono>
  <!-- Look in the standard install locations -->
  <BaseFrameworkPathOverrideForMono Condition="'$(BaseFrameworkPathOverrideForMono)' == '' AND '$(TargetIsMono)' == 'true' AND EXISTS('/Library/Frameworks/Mono.framework/Versions/Current/lib/mono')">/Library/Frameworks/Mono.framework/Versions/Current/lib/mono</BaseFrameworkPathOverrideForMono>
  <BaseFrameworkPathOverrideForMono Condition="'$(BaseFrameworkPathOverrideForMono)' == '' AND '$(TargetIsMono)' == 'true' AND EXISTS('/usr/lib/mono')">/usr/lib/mono</BaseFrameworkPathOverrideForMono>
  <BaseFrameworkPathOverrideForMono Condition="'$(BaseFrameworkPathOverrideForMono)' == '' AND '$(TargetIsMono)' == 'true' AND EXISTS('/usr/local/lib/mono')">/usr/local/lib/mono</BaseFrameworkPathOverrideForMono>
  <!-- If we found Mono reference assemblies, then use them -->
  <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net45'">$(BaseFrameworkPathOverrideForMono)/4.5-api</FrameworkPathOverride>
  <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net451'">$(BaseFrameworkPathOverrideForMono)/4.5.1-api</FrameworkPathOverride>
  <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net452'">$(BaseFrameworkPathOverrideForMono)/4.5.2-api</FrameworkPathOverride>
  <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net46'">$(BaseFrameworkPathOverrideForMono)/4.6-api</FrameworkPathOverride>
  <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net461'">$(BaseFrameworkPathOverrideForMono)/4.6.1-api</FrameworkPathOverride>
  <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net462'">$(BaseFrameworkPathOverrideForMono)/4.6.2-api</FrameworkPathOverride>
  <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net47'">$(BaseFrameworkPathOverrideForMono)/4.7-api</FrameworkPathOverride>
  <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net471'">$(BaseFrameworkPathOverrideForMono)/4.7.1-api</FrameworkPathOverride>
  <EnableFrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != ''">true</EnableFrameworkPathOverride>
  <!-- Add the Facades directory.  Not sure how else to do this. Necessary at least for .NET 4.5 -->
  <AssemblySearchPaths Condition="'$(BaseFrameworkPathOverrideForMono)' != ''">$(FrameworkPathOverride)/Facades;$(AssemblySearchPaths)</AssemblySearchPaths>
</PropertyGroup>

Rumour has it this snippet was first published by https://github.com/dsyme. Thanks Don! :thumbsup:.

Drumroll again …
“`
error CS0012: The type ‘Attribute’ is defined in an assembly that is not referenced. You must add a reference to assembly ‘System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a’.
“`
Alas. Looks like we’ll have to deal with those System references after all. But rather than going back to the packages.config, let’s just follow the compiler error message and add:

Now I get a new error saying I need System.Threading.Tasks version 4.0.0.0 as well, so I add that and… Success!
I can see now that the project bin/Debug directory contains builds for each each target:
“`
chris ~/Source/Repos/TestBase]ls PredicateDictionary.Specs/bin/Debug/
net45 netcoreapp2.0
“`
And when I run the Unit Tests in Rider or Visual Studio, I get two lots of tests.

The final `csproj` file looks like this. It builds both framework and netcore targets, and works on Windows, on macOs with Mono 5, and on Linux with mono 5.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>netcoreapp2.0;net45</TargetFrameworks>
    <IsPackable>True</IsPackable>
    <IsTestProject>True</IsTestProject>
  </PropertyGroup>

  <ItemGroup Condition="$(TargetFramework.StartsWith('netcore'))">
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.8.0" />
    <PackageReference Include="xunit" Version="2.4.0" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
  </ItemGroup>

  <ItemGroup Condition="$(TargetFramework.StartsWith('net4'))" >
    <PackageReference Include="xunit" Version="2.4.1" />
    <PackageReference Include="xunit.abstractions" Version="2.0.3" />
    <PackageReference Include="xunit.analyzers" Version="0.10.0" />
    <PackageReference Include="xunit.assert" Version="2.4.1" />
    <PackageReference Include="xunit.core" Version="2.4.1" />
    <PackageReference Include="xunit.extensibility.core" Version="2.4.1" />
    <PackageReference Include="xunit.extensibility.execution" Version="2.4.1" />
    <Reference Include="System.Runtime" Version="4.0.0.0" />
    <Reference Include="System.Threading.Tasks" Version="4.0.0.0" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\PredicateDictionary\PredicateDictionary.csproj" />
    <ProjectReference Include="..\TestBase\TestBase.csproj" />
  </ItemGroup>

  <PropertyGroup>
    <!-- When compiling .NET SDK 2.0 projects targeting .NET 4.x on Mono using 'dotnet build' you -->
    <!-- have to teach MSBuild where the Mono copy of the reference asssemblies is -->
    <TargetIsMono Condition="$(TargetFramework.StartsWith('net4')) and '$(OS)' == 'Unix'">true</TargetIsMono>
    <!-- Look in the standard install locations -->
    <BaseFrameworkPathOverrideForMono Condition="'$(BaseFrameworkPathOverrideForMono)' == '' AND '$(TargetIsMono)' == 'true' AND EXISTS('/Library/Frameworks/Mono.framework/Versions/Current/lib/mono')">/Library/Frameworks/Mono.framework/Versions/Current/lib/mono</BaseFrameworkPathOverrideForMono>
    <BaseFrameworkPathOverrideForMono Condition="'$(BaseFrameworkPathOverrideForMono)' == '' AND '$(TargetIsMono)' == 'true' AND EXISTS('/usr/lib/mono')">/usr/lib/mono</BaseFrameworkPathOverrideForMono>
    <BaseFrameworkPathOverrideForMono Condition="'$(BaseFrameworkPathOverrideForMono)' == '' AND '$(TargetIsMono)' == 'true' AND EXISTS('/usr/local/lib/mono')">/usr/local/lib/mono</BaseFrameworkPathOverrideForMono>
    <!-- If we found Mono reference assemblies, then use them -->
    <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net45'">$(BaseFrameworkPathOverrideForMono)/4.5-api</FrameworkPathOverride>
    <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net451'">$(BaseFrameworkPathOverrideForMono)/4.5.1-api</FrameworkPathOverride>
    <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net452'">$(BaseFrameworkPathOverrideForMono)/4.5.2-api</FrameworkPathOverride>
    <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net46'">$(BaseFrameworkPathOverrideForMono)/4.6-api</FrameworkPathOverride>
    <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net461'">$(BaseFrameworkPathOverrideForMono)/4.6.1-api</FrameworkPathOverride>
    <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net462'">$(BaseFrameworkPathOverrideForMono)/4.6.2-api</FrameworkPathOverride>
    <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net47'">$(BaseFrameworkPathOverrideForMono)/4.7-api</FrameworkPathOverride>
    <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net471'">$(BaseFrameworkPathOverrideForMono)/4.7.1-api</FrameworkPathOverride>
    <EnableFrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != ''">true</EnableFrameworkPathOverride>
    <!-- Add the Facades directory.  Not sure how else to do this. Necessary at least for .NET 4.5 -->
    <AssemblySearchPaths Condition="'$(BaseFrameworkPathOverrideForMono)' != ''">$(FrameworkPathOverride)/Facades;$(AssemblySearchPaths)</AssemblySearchPaths>
  </PropertyGroup>  
</Project>

Coding for multi-targeting.

People have been doing this for decades, and there are plenty of C# examples on NuGet, so I add only a couple of brief notes for beginners.

A Polyfill for DBNull

This polyfill using runtime type checking instead of conditional compilation:

    /// <summary>
    ///     Polyfill for the fact that NetStandard 1.3 is missing System.DBNull, although NetFx has it since v1.1
    /// </summary>
    class DBNull
    {
        /// <summary>
        ///     Polyfill for System.DBNull.Value, because NetStandard 1.3 is missing System.DBNull, although NetFx has it since
        ///     v1.1
        /// </summary>
        public static readonly object Value = Type.GetType("System.DBNull")
                                                 ?.GetField("Value", BindingFlags.Public | BindingFlags.Static)
                                                 ?.GetValue(null);
    }

Polyfills for Type.GetTypeInfo() & PropertyInfo.GetCustomAttribute()

In dotnet core System.Type.GetTypeInfo() provides methods which, under Framework 4, you would call directly off the System.Type itself. In fact GetTypeInfo() exists in Net45 onwards, so I’ve used this for targetting net40:

#if NET40
/// <summary>
/// Extension methods to ease net40«--»netstandard code sharing.
/// Backfills methods in NetStandard but not in Net40
/// </summary>
public static class MultiTargetShims
{
    /// <summary>Shim for <c>GetTypeInfo()</c>, returns the <see cref="Type"/> instead</summary>
    /// <param name="type"></param>
    /// <returns><paramref name="type"/></returns>
    public static Type GetTypeInfo(this Type type) => type;
    /// <summary>Shim for <c>GetCustomAttributes&lt;T%gt;</c>
    /// using <see cref="MemberInfo.GetCustomAttributes(Type,bool)"/> </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="this"></param>
    /// <returns>
    /// <c><![CDATA[@this.GetCustomAttributes(typeof(T),true).Cast<T>().FirstOrDefault()]]></c>
    /// </returns>
    public static T GetCustomAttribute<T>(this PropertyInfo @this) where T : Attribute
    {
        return @this.GetCustomAttributes(typeof(T),true).Cast<T>().FirstOrDefault();
    }
}
#endif

Polyfill for FastExpressionCompiler

I use the NuGet package FastExpressionCompiler, which requires Net45 or NetStandard. For Net40, I replace it with just the Framework Expression.Compile(). Each file that references CompileFast() has a conditional using statement:

#if NETSTANDARD
using FastExpressionCompiler;
#endif

And I add an extension method for the Net40 only:

#if !NETSTANDARD
public static class MultiTargetShims
{
    /// <summary>Replace FastCompiler with <see cref="Expression{TDelegate}.Compile()"/></summary>
    /// <param name="expression"></param>
    /// <typeparam name="T"></typeparam>
    /// <returns>T</returns>
    public static T CompileFast<T>(this Expression<T> expression) => expression.Compile();
}
#endif

You can get more sophisticated and use e.g. `#if NET40 && !NET45` but my main concern is targetting netcore+netframework, rather than coding for different versions of netframework.

Adding your Outlook.com account to MacOs as an Exchange account.

Is simple when you know how. Possibly harder if, like me, your outlook login is not an outlook.com email address, but your own domain.

System Preferences -> Add Internet Account -> Choose the Big Exchange Button

MacOs System Preferences with “Internet Accounts” Highlighted

MacOs System Preferences with “Internet Accounts” Highlighted

First, get an app-password from your outlook.com account
Then note the Exchange server URL: https://outlook.office365.com/EWS/Exchange.asmx
Then, try to add your account just by typing in your email address and the app-password you got.
If that doesn’t work, and you get the “Unable to verify account name or password” paste the Exchange server URL into the two boxes for Internal URL and External URL:

MacOs Mojave Add an Outlook.com account

MacOs Mojave Add an Outlook.com account

And that works for me™ on MacOs Mojave in 2019.

MacBook Pro 2011 17″ Disable Broken Radeon Graphics Card and Force use Integrated Graphics

If you own one of the last generation of 17″ MacBook Pros from 2011, and if you have worked with a large external monitor, you may reach the point of frying the Radeon graphics card.

Since the model also has built-in Intel graphics, the question arises, can I not carry on working with that?

Yes you can.

This script helps you to disable the card at boot, and force use of the Intel integrated graphics. And, to re-run that process after an O/S update has undone the changes. The loss is that you can no longer plug an external monitor in. The gain is that your MacBook 17″ now works again, and runs slightly cooler/at lower power to boot.

Several steps are involved. I wrote a script to reduce the steps to:

1. Boot to recovery mode and run the script
2. Boot to single user mode and run the script
3. Optionally boot to recover mode again to re-able SIP.

Recall that to run the script you must make it executable: chmod a+x force-integrated-graphics.sh

But how do I use this if I’ve already broken the graphics card?

* You should still be able to use the Cmd-S, or Cmd-RS keypresses at boot to get to a command line. If you can get from there to a pendrive or a network drive, then you can copy the script.

https://gist.github.com/chrisfcarroll/ff8ad18be53b0391464a9affeb119364

#! /bin/sh

kextoffdir="/kextoff"
loginhookscript="/Library/LoginHook/LoadDelayedAMDRadeonX3000kext.sh"

echo "---------------------------------------------------------------
Run this script twice.
First, from a Recovery Mode commandline to run csrutil disable.
Second, after rebooting to a Single user mode commandline, with / partition mounted writeable, to do all the things that must be done with csrutil status= disabled

After that, rerun Recovery mode to run \`csrutil enable\` again.

* How to start in Recovery mode? Cmd-R

* How to start in Single user mode? Cmd-S

---------------------------------------------------------------
"

echo "
nb: csrutil disable only works in recovery mode
"
csrutil disable
echo "
csrstatus:
"
csrutil status

echo "
Moving the AMDRadeonX3000.kext out of extensions and updating the extensions cache
"
mount -uw /
mkdir -p $kextoffdir
mv /System/Library/Extensions/AMDRadeonX3000.kext/ $kextoffdir/
touch /System/Library/Extensions/

echo "
Set nvram magic things. This is the step that disables the Radeon as the bootup graphics card
"
nvram 'fa4ce28d-b62f-4c99-9cc3-6815686e30f9:gpu-power-prefs=%01%00%00%00'
nvram boot-args="-v"
nvram fa4ce28d-b62f-4c99-9cc3-6815686e30f9:gpu-power-prefs
nvram -p

echo "
Add a loginHook to load AMDRadeonX3000.kext after boot
"
mkdir -p /Library/LoginHook

cat > /Library/LoginHook/LoadDelayedAMDRadeonX3000kext.sh <

Kudos and References

* https://apple.stackexchange.com/questions/166876/macbook-pro-how-to-disable-discrete-gpu-permanently-from-efi
* https://github.com/codykrieger/gfxCardStatus

ConEmu in your Visual Studio External Tools List

Add ConEmu to your VisualStudio Menu -> Tools -> External:

Visual Studio Menu -> Tools -> External

VisualStudio Menu -> Tools -> External

By copying the line from ConEmu’s shell integration to get:

Command: C:\Program Files\ConEmu\ConEmu64.exe
Arguments: "{PowerShell} -cur_console:n"
Initial Directory: $(ProjectDir)

ConEmu in the Visual Studio ExternalTools menu

ConEmu in your Visual Studio ExternalTools menu