Resolving FileNotFoundException when using Simple.OData.Client in iOS applications

Several users of Simple.OData.Client 4.0 pre-release reported about an error when using the library on iOS platform. An attempt to send an OData request resulted in FileNotFoundException with the error message about the V3 or V4 adapter, e.g.:

System.IO.FileNotFoundException: Could not load file or assembly 'Simple.OData.Client.V3.Adapter' or one of its dependencies.

This error occurs only on iOS platform, and it is caused by application size optimization performed by the iOS linker. The linker scans applications dependencies and strips out all unused assemblies. Or should I say – assemblies it suspects to be unused. To reduce memory footprint, Simple.OData.Client 4.0 loads only assemblies that are required by the selected version of OData protocol, and the root adapter assembly is loaded by reflection making the iOS linker believe that it’s unused.

This is in fact well-documented behavior, and there are several workarounds. Some of them requires marking code to be preserved with special attributes, but Simple.OData.Client is a packaged as a portable class library, so it can’t have dependencies on platform-specific definitions. An alternative is to include in the main application assembly a single line of code referencing the OData adapter assembly. Here’s how you do it if your application uses OData services over V3 protocol:


And of the code for V4 protocol is similar:


Note that this code does nothing. Absolutely nothing. So you can place it anywhere within the iOS application assembly. And it’s only iOS apps that need it. Android, Windows Phone and desktop applications will run fine without an explicit adapter reference.

Finding a suitable PCL profile for a cross-platform library

I recently posted on StackOverflow a question about finding a right profile for a .NET portable class library. For a long time I have packaged Simple.OData.Client, an open source OData client library that I wrote and maintain, as a PCL using one of the broadest possible profiles (Profile147):


Since Profile147 supports .NET framework starting from the version 4.0.3, I had to include in the NuGet package a platform-specific version for .NET 4.0. Later I also added a platform-specific version for Silverlight 5, because I experienced problems with using HttpClient from a PCL targeting Silverlight. So in practice I could change the profile for my library to a more narrow one (Profile78):


But I kept the original profile, mainly to ensure my PCL can be referenced by a largest possible set of other PCLs. When a portable class library references another PCL, it can not target a wider set of platforms than the refenced PCL. So I wanted to cover a case when somebody would write a PCL that targets Silverlight 5 and references Simple.OData.Client.

But targeting legacy platforms comes at some costs. Legacy platforms may lack or have different API signatures (e.g. reflection API), and use of async/await keywords requires installing Microsoft.Bcl.Async NuGet package. More packages, more dependencies, different API revisions. And there’s more. Microsoft’s David Kean, answering a question about .NET portable subsets, uncovered some of the internals: “For legacy platforms (Phone 7.x, SL4/5, .NET 4, Xbox), when we come up with the intersections between multiple platforms, we need to physically generate actual assemblies that represent the common APIs. For example, when you combine Windows Phone 7 and .NET Framework, we generate (these are generated on our side in Microsoft) an actual mscorlib, system, system.core, etc that contain the APIs that these share. This is not only very time consuming, it’s also extremely problematic in that it can generate not very useful subsets.”

David also answered my original question and among other things wrote the following: “Looking at the data internally, I can see that “Profile78” (.NET Framework 4.5, Windows 8, Phone 8, Xamarin.Android, Xamarin.iOS) is overwhelming the most popular target (~45% of portable projects target this). This is not surprising. It targets large breadth of modern platforms with a reasonable large feature support. These platforms that this profile targets, also support what we consider our “modern surface area”, hence have way more shared API surface vs profiles that target platforms that support our legacy surface area (Silverlight, .NET Framework 4).”

It all convinced me that it’s time to revise my profile selection strategy and target the modern surface area. The fact that 45% of portable libraries choose Profile78 also indicates that my original intention to support wider set of platforms for the sake of transitive PCL support wasn’t practically justified: developer move on and target newer platforms.

And to illustrate some other consequences of choosing the legacy surface area, I made a few tests using Xamarin HttpClient sample. The sample contains a core PCL that demonstrates HttpClient capabilities and two mobile applications (for iOS and Android) using that PCL. The original sample uses Profile7 (.NET 4.5 + Windows Store). I built it and successfully ran on both iOS and Android simulators. Then I changed the profile to Profile78 (added Windows Phone 8). Tried to build again, but compilation failed:

xamarin-samples\HttpClientPortable\HttpPortable\NetHttp.cs(18,46,18,56): error CS0234: The type or namespace name 'HttpClient' does not exist in the namespace 'System.Net.Http' (are you missing an assembly reference?)
xamarin-samples\HttpClientPortable\HttpPortable\NetHttp.cs(19,13,19,62): error CS1502: The best overloaded method match for 'HttpPortable.IRenderer.RenderStream(System.IO.Stream)' has some invalid arguments
xamarin-samples\HttpClientPortable\HttpPortable\NetHttp.cs(19,29,19,61): error CS1503: Argument 1: cannot convert from 'void' to 'System.IO.Stream'

So adding Windows Phone 8 requires installing HttpClient NuGet package (Microsoft.Net.Http). I did it from a Package Manager window:

PM> install-package Microsoft.Net.Http
Attempting to resolve dependency 'Microsoft.Bcl (≥ 1.1.3)'.
Attempting to resolve dependency 'Microsoft.Bcl.Build (≥ 1.0.10)'.
Installing 'Microsoft.Bcl.Build 1.0.14'.
Successfully installed 'Microsoft.Bcl.Build 1.0.14'.
Installing 'Microsoft.Bcl 1.1.7'.
Successfully installed 'Microsoft.Bcl 1.1.7'.
Installing 'Microsoft.Net.Http 2.2.19'.
Successfully installed 'Microsoft.Net.Http 2.2.19'.
Adding 'Microsoft.Bcl.Build 1.0.14' to HttpPortable.
Successfully added 'Microsoft.Bcl.Build 1.0.14' to HttpPortable.
Adding 'Microsoft.Bcl 1.1.7' to HttpPortable.
Successfully added 'Microsoft.Bcl 1.1.7' to HttpPortable.
Adding 'Microsoft.Net.Http 2.2.19' to HttpPortable.
Successfully added 'Microsoft.Net.Http 2.2.19' to HttpPortable.

As you see, Microsoft.Net.Http dragged two other packages: Microsoft.Bcl and Microsoft.Bcl.Build. Then applications successfully built and ran.

Next I added Silverlight to the set of supported platforms (Profile158). Silverlight belongs to a legacy surface area, which causes physical generation of code representing the common APIs, as David wrote above. And it requires installing a new NuGet package, because at first the code didn’t compile:

xamarin-samples\HttpClientPortable\HttpPortable\NetHttp.cs(19,29,19,61): error CS4001: Cannot await 'System.Threading.Tasks.Task<System.IO.Stream>'
xamarin-samples\HttpClientPortable\HttpPortable\NetHttp.cs(19,13,19,62): error CS1502: The best overloaded method match for 'HttpPortable.IRenderer.RenderStream(System.IO.Stream)' has some invalid arguments
xamarin-samples\HttpClientPortable\HttpPortable\NetHttp.cs(19,29,19,61): error CS1503: Argument 1: cannot convert from 'void' to 'System.IO.Stream'

Of course, async/await weren’t originally available on Silverlight. Later Microsoft released another BCL package (Microsoft.Bcl.Async) that I also had to install:

PM> install-package Microsoft.Bcl.Async
Attempting to resolve dependency 'Microsoft.Bcl (≥ 1.0.19)'.
Attempting to resolve dependency 'Microsoft.Bcl.Build (≥ 1.0.10)'.
Installing 'Microsoft.Bcl.Async 1.0.166'.
Successfully installed 'Microsoft.Bcl.Async 1.0.166'.
Adding 'Microsoft.Bcl.Async 1.0.166' to HttpPortable.
Successfully added 'Microsoft.Bcl.Async 1.0.166' to HttpPortable.
After this I was able to build and run the sample applications. I also tried even wider profile (Profile147 with support for .NET 4.0.3) that I was using with Simple.OData.Client, and it built fine using the same set of NuGet packages as Profile158.
And I think I’ve made my choice now. I will continue support .NET 4.0 and Silverlight 5 by offering platform-specific assemblies in the Simple.OData.Client NuGet package, but the core PCL assembly will only support .NET 4.5, Windows Store, Windows Phone 8 (and of course Xamarin iOS and Android). This will ensure it will use modern surface area without dragging large amount of extra code.