After having great success consuming a WCF Web service using MonoTouch as I mentioned in the previous post, nurse I next set off to consume an OData feed thinking this would be just as easy. And sure enough, look it was just as easy, mind until I graduated my wonderful little app from the iPhone Simulator to my actual phone. Once on my phone, the application would still run just fine, but whenever a call was made to asynchronously fetch data from the OData feed - crickets - my user friendly activity indicators just keep spinning indefinitely.
Here are the steps I took to get this far:
- Copied into my MonoTouch project the generated data service reference class from the WP7 project where I previously consumed the OData feed
- Referenced the System.Data.Services.Client assembly provided by MonoTouch
- Put together the following method to asynchronously retrieve a single entity from the OData feed:
public void GetMarker(int markerNum, Action<Marker> successCallback, Action<Exception> errorCallback)
{
var serviceUri = new Uri(ODataServiceUrl);
var dataServiceContext = new MyDataServiceContext(serviceUri);
var markerUri = new Uri(string.Format("/Marker({0})", markerNum), UriKind.Relative);
try
{
dataServiceContext.BeginExecute<Marker>(markerUri, a =>
{
try
{
var results = dataServiceContext.EndExecute<Marker>(a);
var marker = results.Single();
successCallback(marker);
}
catch (Exception e)
{
errorCallback(e);
}
},
null);
}
catch (Exception e)
{
errorCallback(e);
}
} |
As you can see by reviewing the above code, you would think that it is guaranteed that one of the two callbacks (either the successCallback or errorCallback) will always be called. However, launching the Mono Soft Debugger proved otherwise. I placed a breakpoint inside the anonymous delegate passed to the BeginExecute method, and sure enough, it is never reached.
Dumbfounded by what was happening, I rewrote the method to make the call synchronously by replacing the call to BeginExecute with its synchronous counterpart, Execute. It was then that I finally got some visibility into my original issue because the underlying exception was now being caught:
Attempting to JIT compile method '(wrapper managed-to-native) System.Threading.Interlocked:CompareExchange
(System.Exception&,System.Exception,System.Exception)' while running with --aot-only.
at System.Data.Services.Client.BaseAsyncResult.HandleFailure (System.Exception e) [0x00000] in :0
at System.Data.Services.Client.QueryResult.Execute () [0x00000] in :0
at System.Data.Services.Client.DataServiceRequest.Execute[Marker]
(System.Data.Services.Client.DataServiceContext context, System.Data.Services.Client.QueryComponents
queryComponents) [0x00000] in :0
at System.Data.Services.Client.DataServiceContext.Execute[Marker] (System.Uri requestUri) [0x00000] in :0
at Trentacular.Trsh.MarkerService.GetMarkerUsingDataServiceContext (Int32 markerNum, System.Action`1
successCallback, System.Action`1 errorCallback) [0x0002a]
I then found this entry in the MonoTouch troubleshooting documentation that I believe is suggesting that I need to explicitly force the AOT compiler to include the Interlocked.CompareExchange method by calling it myself before the call to Execute. Tried that, same exception still, except it is now getting thrown when I explicitly called the CompareExchange method.
Running out of options, I ditched the DataServiceContext altogether and rewrote the GetMarker method using a WebClient and Linq-to-Xml as follows:
static readonly XNamespace atomNS = XNamespace.Get("http://www.w3.org/2005/Atom");
static readonly XNamespace metadataNS = XNamespace.Get("http://schemas.microsoft.com/ado/2007/08/dataservices/metadata");
static readonly XNamespace dataservicesNS = XNamespace.Get("http://schemas.microsoft.com/ado/2007/08/dataservices");
public void GetMarker(int markerNum, Action<Marker> successCallback, Action<Exception> errorCallback)
{
var webClient = new WebClient();
webClient.DownloadStringCompleted += delegate(object sender, DownloadStringCompletedEventArgs args)
{
try
{
if (args.Error != null)
{
errorCallback(args.Error);
return;
}
var document = XDocument.Parse(args.Result);
var root = document.Root;
var properties = root.Element(atomNS + "content").Element(metadataNS + "properties");
var marker = new Marker
{
MarkerNum = Convert.ToInt32(properties.Element(dataservicesNS + "MarkerNum").Value),
Address = properties.Element(dataservicesNS + "Address").Value.TrimEnd(),
YearEstablished = properties.Element(dataservicesNS + "Year").Value,
MarkerText = properties.Element(dataservicesNS + "MarkerText").Value
};
successCallback(marker);
}
catch (Exception e)
{
errorCallback(e);
}
};
try
{
var markerUri = new Uri(string.Format("{0}/Marker({1})", ODataServiceUrl, markerNum), UriKind.Absolute);
webClient.DownloadStringAsync(markerUri);
}
catch (Exception e)
{
errorCallback(e);
}
} |
This worked liked a charm. It is unfortunate though the DataServiceContext approach doesn’t work. While Linq-to-Xml isn’t all that bad, I am having to do more work than my spoiled .Net developer mentality would like - why traverse xml, deal with xml namespaces, and require knowledge of node names when this can all be abstracted for you.
Knowing the Mono guys are no longer working for Novell, I doubt this will ever get fixed in MonoTouch, but I am hoping that they will produce something far more exceptional with their new venture Xamarin.