- Andrew Lock | .NET Escapades Andrew Lock
- Building .NET Framework ASP.NET Core apps on Linux using Mono and the .NET CLI
- Using Mono for running .NET Core tests on Linux
- Adding FrameworkPathOverrides for Linux
- Adding references to facade libraries
- Fixing errors in xUnit running on Mono
- Summary
- Popular Tags
- First test code compilation release
- Modify the .csproj configuration and add netframework to boot
- remind
- The second test code compiled and released
- Follow-up
Andrew Lock | .NET Escapades Andrew Lock
Sponsored by Nick Chapsas—Want to learn how to build elegant REST APIs in .NET? Get 5% off Nick’s latest course «From Zero to Hero: REST APIs in .NET»!
Building .NET Framework ASP.NET Core apps on Linux using Mono and the .NET CLI
I’ve been hitting Docker hard (as regulars will notice from the topic of recent posts!), and thanks to .NET Core, it’s all been pretty smooth sailing. However, I had a requirement for building a library that multi-tartgets both full .NET framework and .NET Standard, to try to avoid some of the dependency hell you can get into.
Building full .NET framework apps requires that you have .NET Framework installed on your machine (or at least the reference assemblies), which is fine when I’m building locally, as I’m working on Windows. However, I wanted to build my apps in Docker on the build server, which is running on Linux.
I’d played around before with using Mono as a target, but I’d never got very far. However, I recently stumbled across this open issue which contains a number of workarounds. I gave it a try, and evntually got it working!
In this post I’ll describe the steps to get an ASP.NET Core library that targets both .NET Framework and .NET Standard, building, and running tests, on Linux as well as Windows.
tl;dr; Add a .props file to your project and reference it in each project that builds on full framework. You may also need to add explicit references to some Facade assemblies like System.Runtime, System.IO, and System.Threading.Tasks.
Using Mono for running .NET Core tests on Linux
The first point worth making is that I want to be able to run on Linux under the full .NET Framework, not just build. That’s an important distinction, as it means I can run unit tests across all target frameworks on both Windows and Linux.
As discussed by John Skeet in the aforementioned issue, if you just want to build on Linux and target .NET Framework, then you shouldn’t need to install Mono at all — reference assemblies should be sufficient. However, .NET Core tests are executables, which means you need to actually run them. Which brings me back to Mono.
As I described in a previous post, I typically already have Mono installed in my Linux Docker images, as I’m using the full-framework version of Cake (instead of .NET Core-based Cake.CoreClr). My initial reasons for that are less relevant with the recent Cake releases, but as I already have a working build process, I’m not inclined to switch just yet. Especially if I need to use Mono for running tests anyway!
Adding FrameworkPathOverrides for Linux
Unfortunately, installing Mono is only the first hurdle you’ll face if you try and build your multi-targeted .NET Core apps on Linux. If you just try running the build without changing your project, you’ll get an error something like the following:
error MSB3644: The reference assemblies for framework «.NETFramework,Version=v4.5.1» were not found. To resolve this, install the SDK or Targeting Pack for this framework version or retarget your application to a version of the framework for which you have the SDK or Targeting Pack installed. Note that assemblies will be resolved from the Global Assembly Cache (GAC) and will be used in place of reference assemblies. Therefore your assembly may not be correctly targeted for the framework you intend.
When MSBuild (which the dotnet CLI uses under-the-hood) compiles an application, it needs to use «reference assemblies» so it knows which APIs are actually available for you to call. When you build on Windows, MSBuild knows the standard locations where these libraries can be found, but for building on Mono, it needs help.
That’s where the following .props file comes in. This file (courtesy of this comment on GitHub), when referenced by a project, looks in the common install locations for Mono and sets the FrameworkPathOverride property as appropriate. MSBuild uses this property to locate the Framework libraries required to build your app.
Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> PropertyGroup> TargetIsMono Condition="$(TargetFramework.StartsWith('net4')) and '$(OS)' == 'Unix'">trueTargetIsMono> BaseFrameworkPathOverrideForMono Condition="'$(BaseFrameworkPathOverrideForMono)' == '' AND '$(TargetIsMono)' == 'true' AND EXISTS('/Library/Frameworks/Mono.framework/Versions/Current/lib/mono')">/Library/Frameworks/Mono.framework/Versions/Current/lib/monoBaseFrameworkPathOverrideForMono> BaseFrameworkPathOverrideForMono Condition="'$(BaseFrameworkPathOverrideForMono)' == '' AND '$(TargetIsMono)' == 'true' AND EXISTS('/usr/lib/mono')">/usr/lib/monoBaseFrameworkPathOverrideForMono> BaseFrameworkPathOverrideForMono Condition="'$(BaseFrameworkPathOverrideForMono)' == '' AND '$(TargetIsMono)' == 'true' AND EXISTS('/usr/local/lib/mono')">/usr/local/lib/monoBaseFrameworkPathOverrideForMono> FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net45'">$(BaseFrameworkPathOverrideForMono)/4.5-apiFrameworkPathOverride> FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net451'">$(BaseFrameworkPathOverrideForMono)/4.5.1-apiFrameworkPathOverride> FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net452'">$(BaseFrameworkPathOverrideForMono)/4.5.2-apiFrameworkPathOverride> FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net46'">$(BaseFrameworkPathOverrideForMono)/4.6-apiFrameworkPathOverride> FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net461'">$(BaseFrameworkPathOverrideForMono)/4.6.1-apiFrameworkPathOverride> FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net462'">$(BaseFrameworkPathOverrideForMono)/4.6.2-apiFrameworkPathOverride> FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net47'">$(BaseFrameworkPathOverrideForMono)/4.7-apiFrameworkPathOverride> FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net471'">$(BaseFrameworkPathOverrideForMono)/4.7.1-apiFrameworkPathOverride> EnableFrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != ''">trueEnableFrameworkPathOverride> AssemblySearchPaths Condition="'$(BaseFrameworkPathOverrideForMono)' != ''">$(FrameworkPathOverride)/Facades;$(AssemblySearchPaths)AssemblySearchPaths> PropertyGroup> Project>
You could copy this into each .csproj file, but a better approach is to put it into a file in your root directory, netfx.props for example, and import it into each project file. For example:
Project Sdk="Microsoft.NET.Sdk"> Import Project="..\netfx.props" /> PropertyGroup> TargetFrameworks>net452;netsandard2.0TargetFrameworks> PropertyGroup> Project>
Note, I tried to use Directory.Build.props to automatically import the file into every project, but I couldn’t get it to work. I’m guessing the properties are imported at the wrong time, so I think you’ll have to stick to the manual approach.
With the path to the framework libraries overwritten, you’re one step closer to running full framework on Linux, but you’re not quite there yet.
Adding references to facade libraries
If you try the above solutions in your own projects, you’ll likely see a different set of errors, complaining about missing basic types like Attribute , Task , or Stream :
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
To fix these errors, you need to add references to the indicated assemblies to your projects. You can add these libraries using a conditional, so they’re only referenced when building full .NET Framework apps, but not .NET Standard or .NET Core apps:
Project Sdk="Microsoft.NET.Sdk"> Import Project="..\..\netfx.props" /> PropertyGroup> TargetFrameworks>net452;netstandard1.5TargetFrameworks> PropertyGroup> ItemGroup Condition=" '$(TargetFramework)' == 'net452' "> Reference Include="System" /> Reference Include="System.IO" /> Reference Include="System.Runtime" /> Reference Include="System.Threading.Tasks" /> ItemGroup> Project>
We’re getting closer, the app builds now, but if you’re running your tests with xUnit (as I was) then you’ll likely see exceptions when running your tests with dotnet test .
Fixing errors in xUnit running on Mono
After adding the required facade assembly references to my test projects, I was seeing the following error in the test phase of my app build, a NullReferenceException in System.Runtime.Remoting :
Catastrophic failure: System.NullReferenceException: Object reference not set to an instance of an object Server stack trace: at System.Runtime.Remoting.ClientIdentity.get_ClientProxy () [0x00000] in :0 at System.Runtime.Remoting.RemotingServices.GetOrCreateClientIdentity (System.Runtime.Remoting.ObjRef objRef, System.Type proxyType, System.Object& clientProxy) [0x00068] in :0 at System.Runtime.Remoting.RemotingServices.GetRemoteObject (System.Runtime.Remoting.ObjRef objRef, System.Type proxyType) [0x00000] in :0
Apparently this is due to some long-standing bugs in Mono related to app domains. The simplest solution was to just disable app domains for my tests.
To disable app domains, add an xunit.runner.json file to your test project, containing the following content. If you already have a xunit.runner.json file, add the appDomain property.
Ensure the file is copied to the build output by referencing it in your test project’s .csproj file with the CopyToOutputDirectory directory set to PreserveNewest or Always :
ItemGroup> Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" /> ItemGroup>
With these changes, I was finally able to get full .NET Framework tests running on Linux, in addition to my .NET Core tests. You can see an example in my NetEscapades.Configuration library, which uses Cake to build the libraries, running on both Windows and Linux using AppVeyor.
Summary
If you want to run tests of your full .NET Framework libraries on Linux, you’ll need to install Mono. You must add a .props file to set the FrameworkPathOverride property, which MSBuild uses to find the Mono assemblies. You may also need to add references to certain facade assemblies. You can add them inside a Condition so they don’t affect your .NET Core builds.
My new book ASP.NET Core in Action, Third Edition is available now! It supports .NET 7.0, and is available as an eBook or paperback. You even get a free copy of the previous editions of ASP.NET Core in Action!
Popular Tags
Want an email when
there’s new posts? Subscribe
Stay up to the date with the latest posts!
First test code compilation release
Eliminate the steps of pulling the code from svn, then use the dotnet command to compile and release the pulled code.
Go to the project’s .csproj directory and execute the following command:
$ dotnet publish -c release --runtime centos.7-x64
Error message found netframework 4.6.1, missing boot.
Modify the .csproj configuration and add netframework to boot
Write the following configuration in the .csproj file for each project
net461 win7-x86;centos.7-x86 /usr/lib/mono/4.6.1-api/
remind
- Because the test uses the project’sTarget FrameworkYesnet461So I willFrameworkPathOverrideThe path refers to the path of the guide mono.
I have referenced the issue of dotnet sdk on github, reference link:
https://github.com/dotnet/sdk/issues/335 - The path **/usr/lib/mono** is the path to the netframework library provided by mono
The second test code compiled and released
$ dotnet publish -c release --runtime centos.7-x64
This time, in addition to some warnings, you can successfully compile and release through dotnet.
Follow-up
We can port the compiled release to jenkins, because Jenkins does not use dotnet to compile the .net plugin, so it can only be compiled with the Execute Shell plugin and the command line.