Recently at Ocuvera, we faced the challenge of adding an iOS app to our offerings. We already had a Xamarin Android app, but we decided to try writing an iOS app using Xamarin Forms instead of Xamarin iOS. Hopefully, at some point in the future we can re-write our Xamarin Android app to use the Xamarin Forms base we are building now.
Our Xamarin Android project referenced some shared .NET Framework 4.6.1 projects. This isn't supposed to be possible - according to all docs I've seen, those projects should have had to been PCLs. Somehow, we got away with referencing our legacy .NET class libraries from Android without any modifications. We quickly found this wouldn't work with Xamarin Forms.
It was time to migrate our shared class libraries to either PCLs or .NET Standard class libraries. Using a Shared Asset Project was not really considered since it would require adding all files in our existing projects as links, one by one. Since .NET Standard is the direction Microsoft is pushing, we decided to give that a try. Little did we know that this would create days of headaches due to small bugs and undocumented use-cases.
The Xamarin docs say that you can use one of three code-sharing strategies: a Shared Asset Project, a Portable Class Library, or a .NET Standard library. There is a template project for each of the first two that is usable by Xamarin, but not for a .NET Standard library.
Creating a .NET Standard Xamarin Forms project
Documentation provided by Xamarin for the .NET Standard code sharing option is almost non-existent. Instead, I followed two blog posts by Oren Novotny: Using Xamarin Forms with .NET Standard VS 2017 Edition and https://oren.codes/2017/01/04/multi-targeting-the-world-a-single-project-to-rule-them-all/. These were both very detailed and useful jumping off points. I'm grateful to the author for their work putting them together.
I created a demo solution where you can follow the commit history to see exactly what I did to recreate this as a test.
The initial steps I followed from Using Xamarin Forms with .NET Standard VS 2017 Edition were:
Create a PCL Cross Platform Forms app (images 1 & 2 for create cross platform app)
Remove the Xamarin.Forms nuget package from the platform-specific csproj.
In Nuget Package Manager settings, select "Allow format selection of first package install" (image "nuget package manager settings.png")
In the platform-specific csproj, add back the Xamarin.Forms Nuget package. Select "Package Reference" as the format. (image "choose nuget package manager format.png")
- Xamarin.Forms should now be listed in the References list under your project, with the Nuget icon. (image "references with nuget icon.png")
Replace the csproj of the PCL with this:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netstandard1.4</TargetFramework> <!-- https://docs.microsoft.com/en-us/nuget/schema/msbuild-targets#packagetargetfallback Allows getting nuget packages that don't explicitly set netstandard version --> <PackageTargetFallback>portable-net45</PackageTargetFallback> <DebugType>full</DebugType> </PropertyGroup> <ItemGroup> <Compile Update="**\*.xaml.cs" DependentUpon="%(Filename)" /> <EmbeddedResource Include="**\*.xaml" SubType="Designer" Generator="MSBuild:UpdateDesignTimeXaml" /> </ItemGroup> </Project>
Delete AssemblyInfo from the PCL (now .NET Standard) project. These properties are generated by Visual Studio now and it will result in duplicates if you don't.
Add the Xamarin.Forms Nuget package to the shared project that now targets netstandard1.4. Using PackageReference format, a line like this will get added to your csproj:
<ItemGroup> <PackageReference Include="Xamarin.Forms" Version="188.8.131.52" /> </ItemGroup>
Delete packages.config from the shared Xamarin Forms project as it will no longer be used.
Build the shared Xamarin Forms project. This should succeed without errors.
You should now be able to build the Forms platform-specific client projects (iOS and Android). To build the Android project, you'll need to have already set up a connection to a Mac build agent.
We chose to target netstandard1.4 because we are still using tooling 1. The table here listing .NET implementation support can be rather confusing at first. Essentially, if you're using .NET Framework 4.6.1, 1.4 is the highest version of .NET Standard you can target using standard (non-preview) versions of Visual Studio.
See the failure
At this point, you can see the failure if you add a project reference in your Xamarin Forms app to a shared .NET Framework library and try to build. You will get an error like this:
error CS0012: The type 'Object' is defined in an assembly that is not referenced. You must add a reference to assembly 'mscorlib, Version=184.108.40.206, Culture=neutral, PublicKeyToken=b77a5c561934e089'.
.NET Standard projects aren't supposed to reference mscorlib - they have NetStandard.library instead. To solve this, we can use multi-targeting so that our shared library can be consumed by both a .NET Framework app as well as a .NET Standard Xamarin Forms app.
Convert library projects' project files
Before attempting to multi-target, you'll want to convert your shared class library projects from the old C# project file style to the new SDK-style project files. It's easiest to replace the entire csproj and start over.
The new, SDK style csproj format removes the need to define DefaultNamespace, AssemblyName and other attributes in AssemblyInfo.cs. Those are generated when the csproj is built. If you were using the defaults for both these, you can simply delete your AssemblyInfo.cs. If you were not using the defaults, you'll want to move the attributes you previously had from AssemblyInfo.cs to your csproj so that they stay the same as they were before. You can now set these from the Properties pane of a project (right click on your project and click Properties, then go to the Package submenu).
Multi-targeting our .NET Standard project
Now you have a legacy .NET Framework client, a .NET Standard Xamarin Forms app, and a .NET Framework class library you'd like to share between them. It's time to move on to Oren Novotny's second blog post: Multi-Targeting the World.
Some of the content in Oren's second blog post is a repeat of what we already did. The important feature we learn about is the ability to change
<TargetFramework>netstandard1.4</TargetFramework> in our shared library's .csproj to
<TargetFrameworks>net461;netstandard1.4</TargetFrameworks>. That line should be changed in all the projects that are shared between Xamarin Forms and .NET Framework projects.
Next in Multi-Targeting the World, Oren explains the
LanguageTargets property to the project file that enables using platform-specific toolsets like Windows Xaml or Android Resources. At first I thought this was necessary if I wanted my shared project to be consumable by an Android project. In fact, it's not required unless you want your shared projects to use platform-specific features. It's probably better to keep platform-specific details like Xaml or Android Resources hidden in platform-specific projects, and exposed using dependency injection. If you intend to keep your shared projects set up this way, the
LanguageTargets property in the project file is unnecessary.
Not all Nuget packages that we use explicitly declare their .NET Standard version support. To let Nuget know that which target version you're looking for, you can use the
We had one shared project which uses some Nuget packages that do not yet explicitly list their support for netstandard1.4. In this project, we had to use
For some reason, we do not have to specify a PackageTargetFallback for Android here, even though this shared project is used by both Xamarin Android and Xamarin Forms iOS projects. This seems to be because the Xamarin Android project is recognized as belonging to .NET 4.6.1.
Another confusing topic is the exact syntax of the various TargetFrameworks and PackageTargetFallbacks one can use. Is it
Xamarin.iOS10, like some blog posts use, or
net461 valid, or should I just use
net46 like I've seen in blog posts? After some digging, I found the schema for Nuget Target Frameworks which answered these questions.
In the solution containing some shared projects and our Xamarin Android/Xamarin Forms clients, we have one misleading error relating to project targeting.
The project 'Mobile.Core' cannot be referenced. The referenced project is targeted to a different framework family (.NETFramework) Source project: iOSClient
Mobile.Core, which iOSClient claims it cannot reference, has this in its csproj:
So, it should be able to be referenced. When we reverse the order so that
netstandard1.4 first, we see the opposite error: the Android project and other projects targeting
net461 claim they cannot reference Mobile.Core because it is targeting a different framework family. The errors are misleading as those projects continue to run code defined in Mobile.Core without a problem.
No post that lists specific bugs and implementation details for tooling is complete without version info, so here's what I have installed:
- Visual Studio 15.2 (26430.13)
- .NET Framework 4.7.02053 installed
- Nuget Package Manager 4.2.0
- Xamarin 220.127.116.116
- Xamarin.Android SDK 18.104.22.168
- Xamarin.iOS and Xamarin Mac SDK 10.10.0.37