Xamarin.Forms使用Azure Mobile Apps SDK的offline sync功能时的痛点介绍

背景

在最近几年中,越来越多的企业会将自己的网站转换成手机端的应用。这里所谓的转换,其实不是摒弃一个使用另一个,而是共存。所以就会有一个比较普遍的需求就是,很多的企业或者开发者希望使用同一套Web API来同时支持自己的网站和手机应用去操作自己的数据库,并且这个数据库的结构也是一样的。

这篇博客就是在阐述一个一模一样的情况。我的一个客户已经基于.NET Core 2.0的版本开发了一套Web API,并且网站已经在运行中。但是他想要用Xamarin.Forms来开发一个手机应用,并且是想用到Azure Mobile Apps SDK来使用离线同步(Offline Sync)功能。

当然,他希望使用同一套Web API和同一个数据库数据结构。

什么是离线同步?

如果是中文,就真的非常好理解。在使用手机的过程中,我们无法保证手机的网络信号永远是畅通的。再假设你现在在乘飞机,手机开启了飞行模式。这时候你需要在你的app中添加一些记录,比如是todoList,你当然希望一旦关闭飞行模式恢复网络之后,这些数据会被同步到数据库。这就是离线同步的意义。

看下面的微软官方解释:

Offline sync allows users to interact with a mobile application, viewing, adding, or modifying data, even where there isn’t a network connection. Changes are stored in a local database, and once the device is online, the changes can be synced with the Azure Mobile Apps instance. This article explains how to add offline sync functionality to a Xamarin.Forms application.

离线同步是如何同Xamarin以及Azure Mobile App SDK工作的?

其实非常简单,如果没有我们现在的一个特殊情况,我们可以直接使用微软官方给出的关于Azure Mobile Apps的Xamarin.Forms的template,这个我们可以从Azure portal下载到。

下载下来之后你会发现它就是一个现成的可以使用的todoList的手机应用,你可以直接按照如下的步骤来开启离线同步功能。

  1. 根据文档Create a Xamarin.Forms app with Azure的步骤来下载集成了Azure Mobile App SDK的Xamarin.Forms的项目。

  2. 根据文档Enable offline sync for your Xamarin.Forms mobile app来开启离线同步的功能。

  3. 根据Synchronizing Offline Data with Azure Mobile Apps来理解怎么使用,调试和测试离线同步的功能。

如果你跟着上面的步骤,你会发现这个sample的项目确实是可以正常工作的。


那么问题出在哪里呢?

问题就出在我的客户希望使用同一套数据库,并且也希望是使用同一套Web API来操作这个数据库的数据。

问题症状

如果仔细阅读上面第三步的那个文档的话,可以发现其实离线同步的关键代码就是调用了这个PushAsync()方法,这个方法定义在SyncAsync()方法中。

具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public async Task SyncAsync()
{
ReadOnlyCollection<MobileServiceTableOperationError> syncErrors = null;

try
{
// Call from the Azure Mobile Apps SDK
await this.client.SyncContext.PushAsync();

// The first parameter is a query name that is used internally by the client SDK to implement incremental sync.
// Use a different query name for each unique query in your program.
await this.todoTable.PullAsync("allTodoItems", this.todoTable.CreateQuery());
}
catch (MobileServicePushFailedException exc)
{
if (exc.PushResult != null)
{
syncErrors = exc.PushResult.Errors;
}
}

// Simple error/conflict handling.
if (syncErrors != null)
{
foreach (var error in syncErrors)
{
if (error.OperationKind == MobileServiceTableOperationKind.Update && error.Result != null)
{
// Update failed, revert to server's copy
await error.CancelAndUpdateItemAsync(error.Result);
}
else
{
// Discard local change
await error.CancelAndDiscardItemAsync();
}

Debug.WriteLine(@"Error executing sync operation. Item: {0} ({1}). Operation discarded.", error.TableName, error.Item["id"]);
}
}
}

Wala,我的客户用了这个代码后,就在foreach循环中得到了错误,错误提示大概是:[0:] Error executing sync operation. Item: WorkDiary (fcddf2b4a5c24e8b8119be2fd3af993c). Operation discarded..

截图:

突破口

这个就是我这篇博客中主要想和大家分享的,是我怎么来troubleshoot这个问题,并且我是怎么找到问题根本原因的。你不会看到很多的代码层面的debugging,因为我的客户感觉比我还会调试代码。其实也是我的客户自己的调试让我很快找到了原因。

一开始做这个case的时候我确实非常迷茫,我看了客户所有和离线同步相关的那部分代码,客户基本就是跟着我们提供的sample来写的,没有私自改动过什么,除了将model object从TodoList改成了自己的WorkDiary之外,其他没有做过什么改动。

但是我的客户为了解决问题,自己去开发了一个LoggingHandler()来记录调用SDK时发出的Web请求。如下:

1
2
3
4
5
6
7
8
9
10
[0:] Request: POST https://xxxxxxxx.azurewebsites.net/tables/WorkDiary
[0:] {"id":"4aa5928a2486404b8e214c82c3a2219a","Company":"default_co","DriverName":"default_driver","Password":"default_pwrd","Vehicle":"default_veh","Fatigue":"default_fat","State":"Start Work","Time":"2018-07-18T00:05:19.492Z","Hour":0,"Min":0,"Anno":"default_anno","Comment":"default_comment"}
[0:] HEADERS
[0:] X-ZUMO-FEATURES:TU,OL
[0:] X-ZUMO-INSTALLATION-ID:07631a2a-dbfd-4965-ad35-3efeb57de7ae
[0:] Accept:application/json
[0:] User-Agent:ZUMO/4.0 (lang=Managed; os=Android; os_version=8.1.0; arch=Unix; version=4.0.2.0)
[0:] X-ZUMO-VERSION:ZUMO/4.0 (lang=Managed; os=Android; os_version=8.1.0; arch=Unix; version=4.0.2.0)
[0:] ZUMO-API-VERSION:2.0.0
[0:] Response: NotFound

我们可以从上面的信息看到,请求的web url是什么,json格式的数据是什么,以及我们返回的response是404 not found。

调试

得到上面的信息之后,我突然之间想到,客户调用的是我们的SDK,但是客户的代码中并没有指定过往哪个地址的Web API去发送请求,我们传递的不过是要更新的数据类型,所以是不是真的他的网站和Xamarin的应用时使用了同一个Web API呢?

要检验这个其实非常简单,我们只需要抓一个Fiddler的网包看一下就知道了。

具体调试步骤如下:

  1. 首先我模拟了客户在Xamarin中发送的web请求,使用Fiddler的Composer的功能。我模拟了一样的数据,一样的web url,并且使用POST的方式来发送请求。

    我得到了和客户的Xamarin应用一样的404 Not Found的结果:

  2. 之后我使用客户的网站来添加了一条记录,并且抓取这个过程中的Fiddler网包,结果:

    并且这个web请求的数据部分是:

从上面两个Fiddler网包的对比,我们可以至少看出2点:

  1. 客户的网站和手机端发出去请求的目的地URL的地址完全是不同的。
  2. 在网站的web请求中,数据结构是Web Forms的那种表单格式,但是在使用Azure Mobile SDK的时候,是Json的数据格式。

从这两点就可以看出这个问题的根本原因了。

解决方案以及痛点

知道了根本原因之后,我找了微软的产品组来寻求帮助。结果得到的结果不是很尽人意,产品组表示,如果是使用Azure Mobile SDK的话,用户的Web API就必须要按照我们的格式来开发,不然是无法用这个SDK的。不尽人意的地方是,连一个workaround都没有。

并且,这个SDK只最高支持到.NET4.6的版本,也无法让用户改自己的.NET Core的Web API来兼容。

最后客户比较善良,愿意为了这个来开发一套新的API,之后我也有帮助客户解决另一个问题是使用这套新的API的,也确认了这样确实可以解决问题。

参考链接:

如果你想要非常详细地了解怎么将Azure Mobile SDK中的功能集成到Xamarin的应用中,那推荐看一下这个微软官方的人出的书:Develop Cloud Connected Mobile Apps with Xamarin and Microsoft Azure

如何在Xamarin.Forms的Secondary ToolbarItem中可以显示图标
You need to set install_url to use ShareThis. Please set it in _config.yml.

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×