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:

Simple.OData.Client.V3Adapter().Reference();

And of the code for V4 protocol is similar:

Simple.OData.Client.V3Adapter().Reference();

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.

Advertisements

Breaking change in Simple.OData.Client 4.0: OData batch processing

One month ago I announced a new major release of Simple.OData.Client, and in this post I would like to describe one of its few breaking changes: executing OData batch operations.

Earlier versions of Simple.OData.Client had a simple but somewhat naive (and incomplete) support for OData batches. Here’s an example:

using (var batch = new ODataBatch(serviceUri))
{
    var client = new ODataClient(batch);
    await client
        .For<Product>()
        .Set(new { ProductName = "Test3", UnitPrice = 21m })
        .InsertEntryAsync(false);
    await client
        .For<Product>()
        .Set(new { ProductName = "Test4", UnitPrice = 22m })
        .InsertEntryAsync(false);
    batch.Complete();
}

This example works fine because it consists only of insert operations and don’t return anything back. While data modification requests represent a primary use of batch operations, OData batch protocol supports data retrieval (GET) operations that can be mixed with POST/PUT/PATCH/DELETE requests in a single batch. And GET requests didn’t work with earlier Simple.OData.Client versions because its batch API was modeled after SQL transaction API that executes individual statements as they come with possibility to rollback them back. OData batch is a different beast: it accumulates multiple operations and sends them at once as a single HTTP POST request once all operations are written into a batch message. Such behavior prevents responses from individual batch operations to be assigned to local variables before the batch is executed. So the code above succeeds in following “tell don’t ask” principle but it will fail if there is a need to ask about something within a batch.

Batch API in Simple.OData.Client 4 is completely rewritten to properly reflect OData batch behavior. It uses a concept of promises which is implemented using C# lambda expressions. The batch operations are recorded as a sequence of Func delegates having signature Func<IODataClient, Task>. So the statement above will look like this:

var batch = new ODataBatch(serviceUri);
batch += c =&gt; c
    .For&lt;Product&gt;()
    .Set(new { ProductName = "Test3", UnitPrice = 21m })
    .InsertEntryAsync(false);
batch += c =&gt; c
    .For&lt;Product&gt;()
    .Set(new { ProductName = "Test4", UnitPrice = 22m })
    .InsertEntryAsync(false);
await batch.ExecuteAsync();

Note that you don’t need to await batch operations as long as they don’t return any results: the operations are not executed instantly, they are recorded in a single batch message that is sent to the server in ExecuteAsync call.

But what if we need to return some data as a part of a batch? Here’s how it can be achieved:

IEnumerable&lt;Airline&gt; airlines1 = null;
IEnumerable&lt;Airline&gt; airlines2 = null;

var batch = new ODataBatch(client);
batch += async c =&gt; airlines1 = await c
    .For&lt;Airline&gt;()
    .FindEntriesAsync();
batch += c =&gt; c
    .For&lt;Airline&gt;()
    .Set(new Airline() { AirlineCode = "TT", Name = "Test Airline"})
    .InsertEntryAsync(false);
batch += async c =&gt; airlines2 = await c
    .For&lt;Airline&gt;()
    .FindEntriesAsync();
await batch.ExecuteAsync();

This code demonstrates how to implement scenario described in Advanced OData Tutorial at odata.org where the first batch operation retrieves a list of airlines, the second creates a new airline record and the third one refreshes the airline list (remember that it’s up to the server to return the updated or original data collection – OData batches are not database transactions, they don’t conform to ACID principles). Note also the use of async/await pattern in both FindEntriesAsync calls. I wrote earlier that you don’t need to await for operations being recorded but if you want to get hold of their responses you will either have to assign them to variables of type Task<T> or use async/await.

OData batch protocol also defines a way to reference newly created resources so they can be used to create associations with other resources. This scenario is also supported by Simple.OData.Client:

var product1 = new Product() {ProductName = "Test1", UnitPrice = 21m};
var product2 = new Product() {ProductName = "Test2", UnitPrice = 22m};

var batch = new ODataBatch(serviceUri);
batch += c =&gt; c
    .For&lt;Product&gt;()
    .Set(product1)
    .InsertEntryAsync(false);
batch += c =&gt; c
    .For&lt;Product&gt;()
    .Set(product2)
    .InsertEntryAsync(false);
batch += async c =&gt; await c
    .For&lt;Category&gt;()
    .Set(new { CategoryName = "Test3", Products = new[] { product1, product2 } })
    .InsertEntryAsync(false);
await batch.ExecuteAsync();

Here you see how Product entries “product1” and “product2” created in the batch are then referenced in the same batch from another new entry of a type Category that creates a link to a collection of associated products identified by the navigation property “Products”.

The revised batch API is available in Simple.OData.Client 4.0 RC6.

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):

Image

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):

Image

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.