如何使用 Azure 移动应用的托管客户端How to use the managed client for Azure Mobile Apps

Note

Visual Studio App Center 正在投资于对移动应用开发至关重要的新集成服务。Visual Studio App Center is investing in new and integrated services central to mobile app development. 开发人员可以使用生成测试分发服务来设置持续集成和交付管道。Developers can use Build, Test and Distribute services to set up Continuous Integration and Delivery pipeline. 部署应用后,开发人员可以使用分析诊断服务监视其应用的状态和使用情况,并使用推送服务与用户互动。Once the app is deployed, developers can monitor the status and usage of their app using the Analytics and Diagnostics services, and engage with users using the Push service. 开发人员还可以利用 Auth 对用户进行身份验证,利用数据服务在云中持久保存和同步应用数据。Developers can also leverage Auth to authenticate their users and Data service to persist and sync app data in the cloud. 立即查看 App CenterCheck out App Center today.

概述Overview

本指南说明如何在 Windows 应用和 Xamarin 应用中使用 Azure 应用服务移动应用的托管客户端库执行常见方案。This guide shows you how to perform common scenarios using the managed client library for Azure App Service Mobile Apps for Windows and Xamarin apps. 如果不熟悉移动应用,最好先完成 Azure Mobile Apps quickstart(Azure 移动应用快速入门)教程。If you are new to Mobile Apps, you should consider first completing the Azure Mobile Apps quickstart tutorial. 在本指南中,我们侧重于客户端托管的 SDK。In this guide, we focus on the client-side managed SDK. 若要详细了解移动应用的服务器端 SDK,请参阅 .NET 服务器 SDKNode.js 服务器 SDK 的文档。To learn more about the server-side SDKs for Mobile Apps, see the documentation for the .NET Server SDK or the Node.js Server SDK.

参考文档Reference documentation

客户端 SDK 的参考文档位于此处:Azure 移动应用 .NET 客户端参考The reference documentation for the client SDK is located here: Azure Mobile Apps .NET client reference. 还可以在 Azure-Samples GitHub 存储库中找到多个客户端示例。You can also find several client samples in the Azure-Samples GitHub repository.

支持的平台Supported Platforms

.NET 平台支持以下平台:The .NET Platform supports the following platforms:

  • 适用于 API 19 到 24 的 Xamarin Android 版本(KitKat 到 Nougat)Xamarin Android releases for API 19 through 24 (KitKat through Nougat)
  • 适用于 iOS 8.0 及更高版本的 Xamarin iOS 版本Xamarin iOS releases for iOS versions 8.0 and later
  • 通用 Windows 平台Universal Windows Platform
  • Windows Phone 8.1Windows Phone 8.1
  • Windows Phone 8.0(Silverlight 应用程序除外)Windows Phone 8.0 except for Silverlight applications

“服务器流”身份验证使用 WebView 显示 UI。The "server-flow" authentication uses a WebView for the presented UI. 如果设备不能呈现 WebView UI,则需要其他身份验证方法。If the device is not able to present a WebView UI, then other methods of authentication are needed. 因此,此 SDK 不适用于手表类型或类似的受限设备。This SDK is thus not suitable for Watch-type or similarly restricted devices.

安装与先决条件Setup and Prerequisites

假设已创建并发布移动应用后端项目(至少包含一个表)。We assume that you have already created and published your Mobile App backend project, which includes at least one table. 在本主题使用的代码中,表的名称为 TodoItem,其中包含以下列:IdTextCompleteIn the code used in this topic, the table is named TodoItem and it has the following columns: Id, Text, and Complete. 此表是完成 Azure 移动应用快速入门时创建的同一表。This table is the same table created when you complete the Azure Mobile Apps quickstart.

相应的类型化客户端 C# 类如下:The corresponding typed client-side type in C# is the following class:

public class TodoItem
{
    public string Id { get; set; }

    [JsonProperty(PropertyName = "text")]
    public string Text { get; set; }

    [JsonProperty(PropertyName = "complete")]
    public bool Complete { get; set; }
}

JsonPropertyAttribute 用于定义客户端字段与表字段之间的 PropertyName 映射。The JsonPropertyAttribute is used to define the PropertyName mapping between the client field and the table field.

若要了解如何在移动应用后端中创建表,请参阅 .NET 服务器 SDK 主题Node.js 服务器 SDK 主题To learn how to create tables in your Mobile Apps backend, see the .NET Server SDK topic or the Node.js Server SDK topic. 如果已在 Azure 门户中使用快速入门项目创建移动应用后端,则也可以在 Azure 门户中使用“简易表” 设置。If you created your Mobile App backend in the Azure portal using the QuickStart, you can also use the Easy tables setting in the Azure portal.

如何:安装托管客户端 SDK 包How to: Install the managed client SDK package

使用以下方法之一从 NuGet 安装移动应用的托管客户端 SDK 包:Use one of the following methods to install the managed client SDK package for Mobile Apps from NuGet:

  • Visual Studio 右键单击项目,单击“管理 NuGet 包” ,搜索 Microsoft.Azure.Mobile.Client 包,并单击“安装” 。Visual Studio Right-click your project, click Manage NuGet Packages, search for the Microsoft.Azure.Mobile.Client package, then click Install.
  • Xamarin Studio 右键单击项目,单击“添加”>“添加 NuGet 包”,搜索 Microsoft.Azure.Mobile.Client 包,并单击“添加包” 。Xamarin Studio Right-click your project, click Add > Add NuGet Packages, search for the Microsoft.Azure.Mobile.Clientpackage, and then click Add Package.

在主活动文件中,请记得添加以下 using 语句:In your main activity file, remember to add the following using statement:

using Microsoft.WindowsAzure.MobileServices;

Note

请注意,在 Android 项目中引用的所有支持包必须都具有相同的版本。Please note that all the support packages referenced in your Android project must have the same version. 对于 Android 平台,SDK 具有 Xamarin.Android.Support.CustomTabs 依赖项,因此,如果你的项目使用较新的支持包,请直接安装具有所需版本的此包以避免冲突。The SDK has Xamarin.Android.Support.CustomTabs dependency for Android platform, so if your project uses newer support packages you need to install this package with required version directly to avoid conflicts.

如何:在 Visual Studio 中使用调试符号How to: Work with debug symbols in Visual Studio

SymbolSource 上提供了 Microsoft.Azure.Mobile 命名空间的符号。The symbols for the Microsoft.Azure.Mobile namespace are available on SymbolSource. 要将 SymbolSource 与 Visual Studio 集成,请参阅 SymbolSource 说明Refer to the SymbolSource instructions to integrate SymbolSource with Visual Studio.

创建移动应用客户端Create the Mobile Apps client

以下代码创建用于访问移动应用后端的 MobileServiceClient 对象。The following code creates the MobileServiceClient object that is used to access your Mobile App backend.

var client = new MobileServiceClient("MOBILE_APP_URL");

在上述代码中,请将 MOBILE_APP_URL 替换为移动应用后端的 URL,可以在 Azure 门户中移动应用后端的边栏选项卡内找到此 URL。In the preceding code, replace MOBILE_APP_URL with the URL of the Mobile App backend, which is found in the blade for your Mobile App backend in the Azure portal. MobileServiceClient 对象应为单一实例。The MobileServiceClient object should be a singleton.

使用表Work with Tables

以下部分详细说明如何搜索和检索记录,以及修改表中的数据。The following section details how to search and retrieve records and modify the data within the table. 本文涵盖以下主题:The following topics are covered:

如何:创建表引用How to: Create a table reference

访问或修改后端表中数据的所有代码都会调用 MobileServiceTable 对象的函数。All the code that accesses or modifies data in a backend table calls functions on the MobileServiceTable object. 调用 GetTable 方法可获取对表的引用,如下所示:Obtain a reference to the table by calling the GetTable method, as follows:

IMobileServiceTable<TodoItem> todoTable = client.GetTable<TodoItem>();

返回的对象使用类型化序列化模型。The returned object uses the typed serialization model. 也支持非类型化的序列化模型。An untyped serialization model is also supported. 下例创建对非类型化表的引用The following example creates a reference to an untyped table:

// Get an untyped table reference
IMobileServiceTable untypedTodoTable = client.GetTable("TodoItem");

在非类型化查询中,必须指定基础 OData 查询字符串。In untyped queries, you must specify the underlying OData query string.

如何:从移动应用中查询数据How to: Query data from your Mobile App

本部分介绍如何向包含以下功能的移动应用后端发出查询:This section describes how to issue queries to the Mobile App backend, which includes the following functionality:

Note

通过强制使用服务器驱动的页大小来防止返回所有行。A server-driven page size is enforced to prevent all rows from being returned. 分页可以防止对大型数据集发出的默认请求对服务造成负面影响。Paging keeps default requests for large data sets from negatively impacting the service. 若要返回 50 行以上,请根据按页返回数据所述使用 SkipTake 方法。To return more than 50 rows, use the Skip and Take method, as described in Return data in pages.

如何:筛选返回的数据How to: Filter returned data

以下代码演示了如何通过在查询中包含 Where 子句来筛选数据。The following code illustrates how to filter data by including a Where clause in a query. 该代码将返回 Complete 属性等于 falsetodoTable 中的所有项。It returns all items from todoTable whose Complete property is equal to false. Where 函数针对该表将一个行筛选谓词应用到查询。The Where function applies a row filtering predicate to the query against the table.

// This query filters out completed TodoItems and items without a timestamp.
List<TodoItem> items = await todoTable
    .Where(todoItem => todoItem.Complete == false)
    .ToListAsync();

可以使用消息检查软件(例如浏览器开发人员工具或 Fiddler)来查看发送到后端的请求的 URI。You can view the URI of the request sent to the backend by using message inspection software, such as browser developer tools or Fiddler. 从请求 URI 中,可以看出查询字符串已修改:If you look at the request URI, notice that the query string is modified:

GET /tables/todoitem?$filter=(complete+eq+false) HTTP/1.1

服务器 SDK 将此 OData 请求转换成 SQL 查询:This OData request is translated into an SQL query by the Server SDK:

SELECT *
    FROM TodoItem
    WHERE ISNULL(complete, 0) = 0

传递给 Where 方法的函数可以包含任意数目的条件。The function that is passed to the Where method can have an arbitrary number of conditions.

// This query filters out completed TodoItems where Text isn't null
List<TodoItem> items = await todoTable
    .Where(todoItem => todoItem.Complete == false && todoItem.Text != null)
    .ToListAsync();

服务器 SDK 会将此示例转换成 SQL 查询:This example would be translated into an SQL query by the Server SDK:

SELECT *
    FROM TodoItem
    WHERE ISNULL(complete, 0) = 0
          AND ISNULL(text, 0) = 0

此查询也可以拆分成多个子句:This query can also be split into multiple clauses:

List<TodoItem> items = await todoTable
    .Where(todoItem => todoItem.Complete == false)
    .Where(todoItem => todoItem.Text != null)
    .ToListAsync();

这两种方法是等效的,可以换用。The two methods are equivalent and may be used interchangeably. 前一个选项(在一个查询中连接多个谓词)更为精简,也是我们推荐的方法。The former option—of concatenating multiple predicates in one query—is more compact and recommended.

Where 子句支持可转换成 OData 子集的操作。The Where clause supports operations that be translated into the OData subset. 运算包括:Operations include:

  • 关系运算符(= =、!=、<、<=、>、>=),Relational operators (==, !=, <, <=, >, >=),
  • 算术运算符(+、-、/、*、%),Arithmetic operators (+, -, /, *, %),
  • 数字精度(Math.Floor、Math.Ceiling),Number precision (Math.Floor, Math.Ceiling),
  • 字符串函数(Length、Substring、Replace、IndexOf、StartsWith、EndsWith),String functions (Length, Substring, Replace, IndexOf, StartsWith, EndsWith),
  • 日期属性(年、月、日、小时、分钟、秒),Date properties (Year, Month, Day, Hour, Minute, Second),
  • 对象的属性访问,以及Access properties of an object, and
  • 组合上述任意运算的表达式。Expressions combining any of these operations.

在考虑服务器 SDK 支持的操作时,可以参考 OData v3 文档When considering what the Server SDK supports, you can consider the OData v3 Documentation.

如何:对返回的数据进行排序How to: Sort returned data

以下代码演示了如何通过在查询中包含 OrderByOrderByDescending 函数来为数据排序。The following code illustrates how to sort data by including an OrderBy or OrderByDescending function in the query. 该代码将返回 todoTable 中已按 Text 字段升序排序的项。It returns items from todoTable sorted ascending by the Text field.

// Sort items in ascending order by Text field
MobileServiceTableQuery<TodoItem> query = todoTable
                .OrderBy(todoItem => todoItem.Text)
List<TodoItem> items = await query.ToListAsync();

// Sort items in descending order by Text field
MobileServiceTableQuery<TodoItem> query = todoTable
                .OrderByDescending(todoItem => todoItem.Text)
List<TodoItem> items = await query.ToListAsync();

如何:返回多页的数据How to: Return data in pages

默认情况下,后端只返回前 50 行。By default, the backend returns only the first 50 rows. 可以通过调用 Take 方法来增加返回的行数。You can increase the number of returned rows by calling the Take method. TakeSkip 方法一起使用可以请求查询返回的总数据集的特定“页”。Use Take along with the Skip method to request a specific "page" of the total dataset returned by the query. 执行以下查询后,将返回表中的前三个项。The following query, when executed, returns the top three items in the table.

// Define a filtered query that returns the top 3 items.
MobileServiceTableQuery<TodoItem> query = todoTable.Take(3);
List<TodoItem> items = await query.ToListAsync();

以下经过修改的查询会跳过前三个结果,返回接下来的三个结果。The following revised query skips the first three results and returns the next three results. 此查询生成数据的第二“页”,其页大小为三个项。This query produces the second "page" of data, where the page size is three items.

// Define a filtered query that skips the top 3 items and returns the next 3 items.
MobileServiceTableQuery<TodoItem> query = todoTable.Skip(3).Take(3);
List<TodoItem> items = await query.ToListAsync();

IncludeTotalCount 方法请求应该返回的 所有 记录的总计数,并忽略指定的任何分页/限制子句:The IncludeTotalCount method requests the total count for all the records that would have been returned, ignoring any paging/limit clause specified:

query = query.IncludeTotalCount();

在实际应用中,可以搭配页导航控件或类似的 UI 使用类似于上述示例的查询,在页之间导航。In a real world app, you can use queries similar to the preceding example with a pager control or comparable UI to navigate between pages.

Note

要替代移动应用后端中的 50 行限制,还必须将 EnableQueryAttribute 应用到公共 GET 方法,并指定分页行为。To override the 50-row limit in a Mobile App backend, you must also apply the EnableQueryAttribute to the public GET method and specify the paging behavior. 将以下语句应用到该方法后,最大返回行数将设置为 1000:When applied to the method, the following sets the maximum returned rows to 1000:

[EnableQuery(MaxTop=1000)]

如何:选择特定列How to: Select specific columns

可以通过在查询中添加 Select 子句来指定要包含在结果中的属性集。You can specify which set of properties to include in the results by adding a Select clause to your query. 例如,以下代码演示了如何做到只选择一个字段,以及如何选择并格式化多个字段:For example, the following code shows how to select just one field and also how to select and format multiple fields:

// Select one field -- just the Text
MobileServiceTableQuery<TodoItem> query = todoTable
                .Select(todoItem => todoItem.Text);
List<string> items = await query.ToListAsync();

// Select multiple fields -- both Complete and Text info
MobileServiceTableQuery<TodoItem> query = todoTable
                .Select(todoItem => string.Format("{0} -- {1}",
                    todoItem.Text.PadRight(30), todoItem.Complete ?
                    "Now complete!" : "Incomplete!"));
List<string> items = await query.ToListAsync();

目前所述的所有函数都是累加式的,因此可以一直链接。All the functions described so far are additive, so we can keep chaining them. 每个链接的调用会影响多个查询。Each chained call affects more of the query. 再提供一个示例:One more example:

MobileServiceTableQuery<TodoItem> query = todoTable
                .Where(todoItem => todoItem.Complete == false)
                .Select(todoItem => todoItem.Text)
                .Skip(3).
                .Take(3);
List<string> items = await query.ToListAsync();

如何:按 ID 查找数据How to: Look up data by ID

使用 LookupAsync 函数可以查找数据库中具有特定 ID 的对象。The LookupAsync function can be used to look up objects from the database with a particular ID.

// This query filters out the item with the ID of 37BBF396-11F0-4B39-85C8-B319C729AF6D
TodoItem item = await todoTable.LookupAsync("37BBF396-11F0-4B39-85C8-B319C729AF6D");

如何:执行非类型化查询How to: Execute untyped queries

使用非类型化的表对象执行查询时,必须通过调用 ReadAsync显式指定 OData 查询字符串,如以下示例中所示:When executing a query using an untyped table object, you must explicitly specify the OData query string by calling ReadAsync, as in the following example:

// Lookup untyped data using OData
JToken untypedItems = await untypedTodoTable.ReadAsync("$filter=complete eq 0&$orderby=text");

此时,将获取一些可以像属性包一样使用的 JSON 值。You get back JSON values that you can use like a property bag. 有关 JToken 和 Newtonsoft Json.NET 的详细信息,请参阅 Json.NET 站点。For more information on JToken and Newtonsoft Json.NET, see the Json.NET site.

如何:将数据插入移动应用后端How to: Insert data into a Mobile App backend

所有客户端类型必须包含名为 Id的成员,其默认为字符串。All client types must contain a member named Id, which is by default a string. 需要此 ID 才能执行 CRUD 操作和脱机同步。以下代码演示如何使用 InsertAsync 方法将新行插入表中。This Id is required to perform CRUD operations and for offline sync. The following code illustrates how to use the InsertAsync method to insert new rows into a table. 参数包含要作为 .NET 对象插入的数据。The parameter contains the data to be inserted as a .NET object.

await todoTable.InsertAsync(todoItem);

如果插入期间没有在 todoItem 中包括唯一的自定义 ID 值,服务器会生成 GUID。If a unique custom ID value is not included in the todoItem during an insert, a GUID is generated by the server. 在调用返回后,可通过检查对象来检索生成的 ID。You can retrieve the generated Id by inspecting the object after the call returns.

若要插入非类型化数据,可利用 Json.NET:To insert untyped data, you may take advantage of Json.NET:

JObject jo = new JObject();
jo.Add("Text", "Hello World");
jo.Add("Complete", false);
var inserted = await table.InsertAsync(jo);

以下示例使用电子邮件地址作为唯一的字符串 ID:Here is an example using an email address as a unique string id:

JObject jo = new JObject();
jo.Add("id", "myemail@emaildomain.com");
jo.Add("Text", "Hello World");
jo.Add("Complete", false);
var inserted = await table.InsertAsync(jo);

使用 ID 值Working with ID values

移动应用支持为表的 ID 列使用唯一的自定义字符串值。Mobile Apps supports unique custom string values for the table's id column. 使用字符串值,应用程序便可为 ID 使用自定义值,例如电子邮件地址或用户名。A string value allows applications to use custom values such as email addresses or user names for the ID. 字符串 ID 可提供以下优势:String IDs provide you with the following benefits:

  • 无需往返访问数据库即可生成 ID。IDs are generated without making a round trip to the database.
  • 更方便地合并不同表或数据库中的记录。Records are easier to merge from different tables or databases.
  • ID 值能够更好地与应用程序的逻辑相集成。IDs values can integrate better with an application's logic.

如果插入的记录中未设置字符串 ID 值,移动应用后端为 ID 生成唯一值。When a string ID value is not set on an inserted record, the Mobile App backend generates a unique value for the ID. 可以在客户端或后端中使用 Guid.NewGuid 方法生成自己的 ID 值。You can use the Guid.NewGuid method to generate your own ID values, either on the client or in the backend.

JObject jo = new JObject();
jo.Add("id", Guid.NewGuid().ToString("N"));

如何:修改移动应用后端中的数据How to: Modify data in a Mobile App backend

以下代码演示如何通过 UpdateAsync 方法使用新信息更新具相同 ID 的现有记录。The following code illustrates how to use the UpdateAsync method to update an existing record with the same ID with new information. 参数包含要作为 .NET 对象更新的数据。The parameter contains the data to be updated as a .NET object.

await todoTable.UpdateAsync(todoItem);

若要更新非类型化数据,可按如下所示利用 Json.NETTo update untyped data, you may take advantage of Json.NET as follows:

JObject jo = new JObject();
jo.Add("id", "37BBF396-11F0-4B39-85C8-B319C729AF6D");
jo.Add("Text", "Hello World");
jo.Add("Complete", false);
var inserted = await table.UpdateAsync(jo);

更新时,必须指定 id 字段。An id field must be specified when making an update. 后端使用 id 字段标识要更新的行。The backend uses the id field to identify which row to update. 可以从 InsertAsync 调用的结果中获取 id 字段。The id field can be obtained from the result of the InsertAsync call. 如果尝试更新项但未提供 id 值,将引发 ArgumentExceptionAn ArgumentException is raised if you try to update an item without providing the id value.

如何:删除移动应用后端中的数据How to: Delete data in a Mobile App backend

以下代码演示如何使用 DeleteAsync 方法删除现有实例。The following code illustrates how to use the DeleteAsync method to delete an existing instance. 可以通过 todoItem 中设置的 id 字段来标识实例。The instance is identified by the id field set on the todoItem.

await todoTable.DeleteAsync(todoItem);

若要删除非类型化数据,可按如下所示利用 Json.NET:To delete untyped data, you may take advantage of Json.NET as follows:

JObject jo = new JObject();
jo.Add("id", "37BBF396-11F0-4B39-85C8-B319C729AF6D");
await table.DeleteAsync(jo);

发出删除请求时,必须指定 ID。When you make a delete request, an ID must be specified. 其他属性不会传递到服务,否则服务会将它们忽略。Other properties are not passed to the service or are ignored at the service. DeleteAsync 调用的结果通常是 nullThe result of a DeleteAsync call is usually null. 可以从 InsertAsync 调用的结果中获取要传入的 ID。The ID to pass in can be obtained from the result of the InsertAsync call. 如果尝试删除项但未指定 id 字段,将引发 MobileServiceInvalidOperationExceptionA MobileServiceInvalidOperationException is thrown when you try to delete an item without specifying the id field.

如何:使用乐观并发解决冲突How to: Use Optimistic Concurrency for conflict resolution

两个或两个以上客户端可能会同时将更改写入同一项目。Two or more clients may write changes to the same item at the same time. 如果没有冲突检测,最后一次写入会覆盖任何以前的更新。Without conflict detection, the last write would overwrite any previous updates. 乐观并发控制 假设每个事务均可以提交,因此不使用任何资源锁定。Optimistic concurrency control assumes that each transaction can commit and therefore does not use any resource locking. 提交事务之前,乐观并发控制验证是否没有其他事务修改了数据。Before committing a transaction, optimistic concurrency control verifies that no other transaction has modified the data. 如果数据已修改,则将回滚正在提交的事务。If the data has been modified, the committing transaction is rolled back.

移动应用通过使用 version 系统属性列(该列是为移动应用后端中的每个表定义的)跟踪对每个项的更改来支持乐观并发控制。Mobile Apps supports optimistic concurrency control by tracking changes to each item using the version system property column that is defined for each table in your Mobile App backend. 每次更新某个记录时,移动应用都将该记录的 version 属性设置为新值。Each time a record is updated, Mobile Apps sets the version property for that record to a new value. 在每次执行更新请求期间,会将该请求包含的记录的 version 属性与服务器上的记录的同一属性进行比较。During each update request, the version property of the record included with the request is compared to the same property for the record on the server. 如果随请求传递的版本与后端不匹配,客户端库会引发 MobileServicePreconditionFailedException<T> 异常。If the version passed with the request does not match the backend, then the client library raises a MobileServicePreconditionFailedException<T> exception. 该异常中提供的类型就是包含记录服务器版本的后端中的记录。The type included with the exception is the record from the backend containing the servers version of the record. 然后,应用程序可以借助此信息来确定是否要使用后端中正确的 version 值再次执行更新请求以提交更改。The application can then use this information to decide whether to execute the update request again with the correct version value from the backend to commit changes.

version 系统属性的表类中定义列,启用乐观并发。Define a column on the table class for the version system property to enable optimistic concurrency. 例如:For example:

public class TodoItem
{
    public string Id { get; set; }

    [JsonProperty(PropertyName = "text")]
    public string Text { get; set; }

    [JsonProperty(PropertyName = "complete")]
    public bool Complete { get; set; }

    // *** Enable Optimistic Concurrency *** //
    [JsonProperty(PropertyName = "version")]
    public string Version { set; get; }
}

使用非类型化表的应用程序通过在表的 SystemProperties 中设置 Version 标志来启用乐观并发,如下所示。Applications using untyped tables enable optimistic concurrency by setting the Version flag on the SystemProperties of the table as follows.

//Enable optimistic concurrency by retrieving version
todoTable.SystemProperties |= MobileServiceSystemProperties.Version;

除启用开放式并发以外,还必须在调用 UpdateAsync 时捕获代码中的 MobileServicePreconditionFailedException<T> 异常。In addition to enabling optimistic concurrency, you must also catch the MobileServicePreconditionFailedException<T> exception in your code when calling UpdateAsync. 将正确的 version 应用到更新的记录,并使用解决的记录调用 UpdateAsync,即可解决冲突。Resolve the conflict by applying the correct version to the updated record and call UpdateAsync with the resolved record. 以下代码演示如何解决检测到的写入冲突:The following code shows how to resolve a write conflict once detected:

private async void UpdateToDoItem(TodoItem item)
{
    MobileServicePreconditionFailedException<TodoItem> exception = null;

    try
    {
        //update at the remote table
        await todoTable.UpdateAsync(item);
    }
    catch (MobileServicePreconditionFailedException<TodoItem> writeException)
    {
        exception = writeException;
    }

    if (exception != null)
    {
        // Conflict detected, the item has changed since the last query
        // Resolve the conflict between the local and server item
        await ResolveConflict(item, exception.Item);
    }
}


private async Task ResolveConflict(TodoItem localItem, TodoItem serverItem)
{
    //Ask user to choose the resolution between versions
    MessageDialog msgDialog = new MessageDialog(
        String.Format("Server Text: \"{0}\" \nLocal Text: \"{1}\"\n",
        serverItem.Text, localItem.Text),
        "CONFLICT DETECTED - Select a resolution:");

    UICommand localBtn = new UICommand("Commit Local Text");
    UICommand ServerBtn = new UICommand("Leave Server Text");
    msgDialog.Commands.Add(localBtn);
    msgDialog.Commands.Add(ServerBtn);

    localBtn.Invoked = async (IUICommand command) =>
    {
        // To resolve the conflict, update the version of the item being committed. Otherwise, you will keep
        // catching a MobileServicePreConditionFailedException.
        localItem.Version = serverItem.Version;

        // Updating recursively here just in case another change happened while the user was making a decision
        UpdateToDoItem(localItem);
    };

    ServerBtn.Invoked = async (IUICommand command) =>
    {
        RefreshTodoItems();
    };

    await msgDialog.ShowAsync();
}

有关详细信息,请参阅 Offline Data Sync in Azure Mobile Apps (Azure 移动应用中的脱机数据同步)主题。For more information, see the Offline Data Sync in Azure Mobile Apps topic.

如何:将移动应用数据绑定到 Windows 用户界面How to: Bind Mobile Apps data to a Windows user interface

本部分说明如何使用 Windows 应用中的 UI 元素显示返回的数据对象。This section shows how to display returned data objects using UI elements in a Windows app. 以下示例代码绑定到具有不完整项查询的列表的源。The following example code binds to the source of the list with a query for incomplete items. MobileServiceCollection 创建感知移动应用的绑定集合。The MobileServiceCollection creates a Mobile Apps-aware binding collection.

// This query filters out completed TodoItems.
MobileServiceCollection<TodoItem, TodoItem> items = await todoTable
    .Where(todoItem => todoItem.Complete == false)
    .ToCollectionAsync();

// itemsControl is an IEnumerable that could be bound to a UI list control
IEnumerable itemsControl  = items;

// Bind this to a ListBox
ListBox lb = new ListBox();
lb.ItemsSource = items;

托管运行时中的某些控件支持名为 ISupportIncrementalLoading的接口。Some controls in the managed runtime support an interface called ISupportIncrementalLoading. 当用户滚动浏览时,此接口允许控件请求更多的数据。This interface allows controls to request extra data when the user scrolls. 系统通过 MobileServiceIncrementalLoadingCollection(可自动处理来自控件的调用)为这个适用于通用 Windows 应用程序的接口提供内置支持。There is built-in support for this interface for universal Windows apps via MobileServiceIncrementalLoadingCollection, which automatically handles the calls from the controls. 在 Windows 应用中使用 MobileServiceIncrementalLoadingCollection ,如下所示:Use MobileServiceIncrementalLoadingCollection in Windows apps as follows:

MobileServiceIncrementalLoadingCollection<TodoItem,TodoItem> items;
items = todoTable.Where(todoItem => todoItem.Complete == false).ToIncrementalLoadingCollection();

ListBox lb = new ListBox();
lb.ItemsSource = items;

若要在 Windows Phone 8 和“Silverlight”应用上使用新的集合,请在 IMobileServiceTableQuery<T>IMobileServiceTable<T> 上使用 ToCollection 扩展方法。To use the new collection on Windows Phone 8 and "Silverlight" apps, use the ToCollection extension methods on IMobileServiceTableQuery<T> and IMobileServiceTable<T>. 若要加载数据,请调用 LoadMoreItemsAsync()To load data, call LoadMoreItemsAsync().

MobileServiceCollection<TodoItem, TodoItem> items = todoTable.Where(todoItem => todoItem.Complete==false).ToCollection();
await items.LoadMoreItemsAsync();

使用通过调用 ToCollectionAsyncToCollection 创建的集合时,可以获取可绑定到 UI 控件的集合。When you use the collection created by calling ToCollectionAsync or ToCollection, you get a collection that can be bound to UI controls. 此集合具有分页感知功能。This collection is paging-aware. 该集合从网络加载数据,因此加载有时候会失败。Since the collection is loading data from the network, loading sometimes fails. 若要处理这种故障,可以重写 MobileServiceIncrementalLoadingCollection 中的 OnException 方法,处理调用 LoadMoreItemsAsync 后发生的异常。To handle such failures, override the OnException method on MobileServiceIncrementalLoadingCollection to handle exceptions resulting from calls to LoadMoreItemsAsync.

假设表包含许多字段,但只想在控件中显示其中的某些字段。Consider if your table has many fields but you only want to display some of them in your control. 可以使用上面“选择特定的列”部分中的指导,选择要在 UI 中显示的特定列。You may use the guidance in the preceding section "Select specific columns" to select specific columns to display in the UI.

更改页面大小Change the Page size

默认情况下,Azure 移动应用针对每个请求最多返回 50 个项。Azure Mobile Apps returns a maximum of 50 items per request by default. 可通过增加客户端和服务器上的最大页面大小来更改分页大小。You can change the paging size by increasing the maximum page size on both the client and server. 若要增加请求的页面大小,请在使用 PullAsync() 时指定 PullOptionsTo increase the requested page size, specify PullOptions when using PullAsync():

PullOptions pullOptions = new PullOptions
    {
        MaxPageSize = 100
    };

假设已在服务器中使 PageSize 等于或大于 100,此时请求最多可返回 100 个项。Assuming you have made the PageSize equal to or greater than 100 within the server, a request returns up to 100 items.

使用脱机表Work with Offline Tables

脱机表使用本地 SQLite 存储来存储数据,供脱机时使用。Offline tables use a local SQLite store to store data for use when offline. 所有表操作都针对本地 SQLite 存储(而不是远程服务器存储)完成。All table operations are done against the local SQLite store instead of the remote server store. 若要创建脱机表,首先应准备项目:To create an offline table, first prepare your project:

  1. 在 Visual Studio 中,右键单击解决方案,再单击“管理解决方案的 NuGet 包…” ,然后在解决方案的所有项目中搜索并安装 Microsoft.Azure.Mobile.Client.SQLiteStore NuGet 包。In Visual Studio, right-click the solution > Manage NuGet Packages for Solution..., then search for and install the Microsoft.Azure.Mobile.Client.SQLiteStore NuGet package for all projects in the solution.

  2. (可选)若要支持 Windows 设备,请安装以下 SQLite 运行时包之一:(Optional) To support Windows devices, install one of the following SQLite runtime packages:

  3. (可选)。(Optional). 对于 Windows 设备,单击“引用” > “添加引用...” ,展开 “Windows”文件夹 >“扩展” ,然后启用相应的 SQLite for Windows SDK 和 Visual C++ 2013 Runtime for Windows SDK。For Windows devices, click References > Add Reference..., expand the Windows folder > Extensions, then enable the appropriate SQLite for Windows SDK along with the Visual C++ 2013 Runtime for Windows SDK. 每个 Windows 平台的 SQLite SDK 名称略有不同。The SQLite SDK names vary slightly with each Windows platform.

创建表引用前,必须先准备本地存储:Before a table reference can be created, the local store must be prepared:

var store = new MobileServiceSQLiteStore(Constants.OfflineDbPath);
store.DefineTable<TodoItem>();

//Initializes the SyncContext using the default IMobileServiceSyncHandler.
await this.client.SyncContext.InitializeAsync(store);

存储初始化通常在创建客户端后立即完成。Store initialization is normally done immediately after the client is created. OfflineDbPath 应该是适合在支持的所有平台上使用的文件名。The OfflineDbPath should be a filename suitable for use on all platforms that you support. 如果路径是完全限定的路径(即以斜杠开头),则将使用该路径。If the path is a fully qualified path (that is, it starts with a slash), then that path is used. 如果不是完全限定的路径,则该文件位于平台特定的位置。If the path is not fully qualified, the file is placed in a platform-specific location.

  • 对于 iOS 和 Android 设备,默认路径为“个人文件”文件夹。For iOS and Android devices, the default path is the "Personal Files" folder.
  • 对于 Windows 设备,默认路径是应用程序特定的“AppData”文件夹。For Windows devices, the default path is the application-specific "AppData" folder.

可以使用 GetSyncTable<> 方法获取表引用:A table reference can be obtained using the GetSyncTable<> method:

var table = client.GetSyncTable<TodoItem>();

无需身份验证即可使用脱机表。You do not need to authenticate to use an offline table. 只需在与后端服务通信时进行身份验证。You only need to authenticate when you are communicating with the backend service.

同步脱机表Syncing an Offline Table

默认情况下,脱机表不与后端同步。Offline tables are not synchronized with the backend by default. 同步拆分为两个部分。Synchronization is split into two pieces. 可通过下载新项单独推送更改。You can push changes separately from downloading new items. 以下是典型的同步方法:Here is a typical sync method:

public async Task SyncAsync()
{
    ReadOnlyCollection<MobileServiceTableOperationError> syncErrors = null;

    try
    {
        await this.client.SyncContext.PushAsync();

        await this.todoTable.PullAsync(
            //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
            "allTodoItems",
            this.todoTable.CreateQuery());
    }
    catch (MobileServicePushFailedException exc)
    {
        if (exc.PushResult != null)
        {
            syncErrors = exc.PushResult.Errors;
        }
    }

    // Simple error/conflict handling. A real application would handle the various errors like network conditions,
    // server conflicts and others via the IMobileServiceSyncHandler.
    if (syncErrors != null)
    {
        foreach (var error in syncErrors)
        {
            if (error.OperationKind == MobileServiceTableOperationKind.Update && error.Result != null)
            {
                //Update failed, reverting 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"]);
        }
    }
}

如果 PullAsync 的第一个参数为 null,则不使用增量同步。If the first argument to PullAsync is null, then incremental sync is not used. 每个同步操作都会检索所有记录。Each sync operation retrieves all records.

SDK 会在提取记录前执行隐式 PushAsync()The SDK performs an implicit PushAsync() before pulling records.

冲突处理会在 PullAsync() 方法上发生。Conflict handling happens on a PullAsync() method. 可以使用与联机表相同的方式处理冲突。You can deal with conflicts in the same way as online tables. 生成在调用 PullAsync() 时生成,而不是在插入、更新或删除期间生成。The conflict is produced when PullAsync() is called instead of during the insert, update, or delete. 如果发生多个冲突,它们将捆绑成单个 MobileServicePushFailedException。If multiple conflicts happen, they are bundled into a single MobileServicePushFailedException. 分别处理每个故障。Handle each failure separately.

使用自定义 APIWork with a custom API

自定义 API 可让你定义自定义终结点,这些终结点会公开不映射到插入、更新、删除或读取操作的服务器功能。A custom API enables you to define custom endpoints that expose server functionality that does not map to an insert, update, delete, or read operation. 使用自定义 API 能够以更大的力度控制消息传送,包括读取和设置 HTTP 消息标头,以及定义除 JSON 以外的消息正文格式。By using a custom API, you can have more control over messaging, including reading and setting HTTP message headers and defining a message body format other than JSON.

通过在客户端上调用其中一个 InvokeApiAsync 方法来调用自定义 API。You call a custom API by calling one of the InvokeApiAsync methods on the client. 例如,以下代码行向后端上的 completeAll API 发送 POST 请求:For example, the following line of code sends a POST request to the completeAll API on the backend:

var result = await client.InvokeApiAsync<MarkAllResult>("completeAll", System.Net.Http.HttpMethod.Post, null);

此表单是类型化方法调用,要求定义 MarkAllResult 返回类型。This form is a typed method call and requires that the MarkAllResult return type is defined. 支持类型化和非类型化的方法。Both typed and untyped methods are supported.

InvokeApiAsync() 方法在想要调用的 API 前附加“/api/”,除非 API 以“/”开头。The InvokeApiAsync() method prepends '/api/' to the API that you wish to call unless the API starts with a '/'. 例如:For example:

  • InvokeApiAsync("completeAll",...) 在后端调用 /api/completeAllInvokeApiAsync("completeAll",...) calls /api/completeAll on the backend
  • InvokeApiAsync("/.auth/me",...) 在后端调用 /.auth/meInvokeApiAsync("/.auth/me",...) calls /.auth/me on the backend

可使用 InvokeApiAsync 调用任意 WebAPI,包括未使用 Azure 移动应用定义的 WebAPI。You can use InvokeApiAsync to call any WebAPI, including those WebAPIs that are not defined with Azure Mobile Apps. 使用 InvokeApiAsync() 时,将随请求一起发送相应的标头(包括身份验证标头)。When you use InvokeApiAsync(), the appropriate headers, including authentication headers, are sent with the request.

对用户进行身份验证Authenticate users

移动应用支持使用外部标识提供者对应用用户进行身份验证和授权:Microsoft 帐户和 Azure Active Directory。Mobile Apps supports authenticating and authorizing app users using external identity provider: Microsoft Account and Azure Active Directory. 可以在表中设置权限,以便将特定操作的访问权限限制给已经过身份验证的用户。You can set permissions on tables to restrict access for specific operations to only authenticated users. 还可以在服务器脚本中使用已经过身份验证的用户的标识来实施授权规则。You can also use the identity of authenticated users to implement authorization rules in server scripts. 有关详细信息,请参阅 向应用程序添加身份验证教程。For more information, see the tutorial Add authentication to your app.

支持两种身份验证流:client-managed 和 server-managed 流 。Two authentication flows are supported: client-managed and server-managed flow. 服务器托管的流依赖于提供者的 Web 身份验证界面,因此可提供最简便的身份验证体验。The server-managed flow provides the simplest authentication experience, as it relies on the provider's web authentication interface. 客户端托管流依赖于提供者和设备特定的 SDK,因此允许与设备特定的功能进行更深入的集成。The client-managed flow allows for deeper integration with device-specific capabilities as it relies on provider-specific device-specific SDKs.

Note

建议在生产应用中使用客户端托管流。We recommend using a client-managed flow in your production apps.

若要设置身份验证,必须向一个或多个标识提供者注册应用。To set up authentication, you must register your app with one or more identity providers. 标识提供者为应用生成客户端 ID 和客户端机密。The identity provider generates a client ID and a client secret for your app. 随后在后端设置这些值,以启用 Azure 应用服务身份验证/授权。These values are then set in your backend to enable Azure App Service authentication/authorization. 有关详细信息,请遵循向应用程序添加身份验证教程中的详细说明。For more information, follow the detailed instructions in the tutorial Add authentication to your app.

本部分介绍以下主题:The following topics are covered in this section:

客户端托管的身份验证Client-managed authentication

应用可以独立联系标识提供者,并在用后端登录期间提供返回的令牌。Your app can independently contact the identity provider and then provide the returned token during login with your backend. 使用此客户端流可为用户提供单一登录体验,或者从标识提供者中检索其他用户数据。This client flow enables you to provide a single sign-on experience for users or to retrieve additional user data from the identity provider. 客户端流身份验证比使用服务器流更有利,因为标识提供者 SDK 提供更自然的 UX 风格,并允许其他自定义。Client flow authentication is preferred to using a server flow as the identity provider SDK provides a more native UX feel and allows for additional customization.

提供了以下客户端流身份验证模式的示例:Examples are provided for the following client-flow authentication patterns:

使用 Active Directory 身份验证库对用户进行身份验证Authenticate users with the Active Directory Authentication Library

可以使用 Active Directory 身份验证库 (ADAL),从使用 Azure Active Directory 身份验证的客户端启动用户身份验证。You can use the Active Directory Authentication Library (ADAL) to initiate user authentication from the client using Azure Active Directory authentication.

  1. 根据如何为 Active Directory 登录配置应用服务教程的说明,为 AAD 登录配置移动应用。Configure your mobile app backend for AAD sign-on by following the How to configure App Service for Active Directory login tutorial. 请务必完成注册本机客户端应用程序的可选步骤。Make sure to complete the optional step of registering a native client application.

  2. 在 Visual Studio 或 Xamarin Studio 中打开项目,并添加对 Microsoft.IdentityModel.Clients.ActiveDirectory NuGet 包的引用。In Visual Studio or Xamarin Studio, open your project and add a reference to the Microsoft.IdentityModel.Clients.ActiveDirectory NuGet package. 搜索时,请包含预发行版。When searching, include pre-release versions.

  3. 根据使用的平台,将以下代码添加到应用程序。Add the following code to your application, according to the platform you are using. 在每条代码中进行以下替换:In each, make the following replacements:

    • INSERT-AUTHORITY-HERE 替换为在其中预配应用程序的租户的名称。Replace INSERT-AUTHORITY-HERE with the name of the tenant in which you provisioned your application. 格式应为 https://login.chinacloudapi.cn/contoso.onmicrosoft.comThe format should be https://login.chinacloudapi.cn/contoso.onmicrosoft.com. 可以在 Azure 门户中从 Azure Active Directory 的域选项卡复制此值。This value can be copied from the Domain tab in your Azure Active Directory in the Azure portal.

    • INSERT-RESOURCE-ID-HERE 替换移动应用后端的客户端 ID。Replace INSERT-RESOURCE-ID-HERE with the client ID for your mobile app backend. 可以在门户中“Azure Active Directory 设置” 下面的“高级” 选项卡获取此客户端 ID。You can obtain the client ID from the Advanced tab under Azure Active Directory Settings in the portal.

    • INSERT-CLIENT-ID-HERE 替换为从本机客户端应用程序复制的客户端 ID。Replace INSERT-CLIENT-ID-HERE with the client ID you copied from the native client application.

    • INSERT-REDIRECT-URI-HERE 替换为站点的 /.auth/login/done 终结点(使用 HTTPS 方案)。Replace INSERT-REDIRECT-URI-HERE with your site's /.auth/login/done endpoint, using the HTTPS scheme. 此值应类似于 https://contoso.chinacloudsites.cn/.auth/login/doneThis value should be similar to https://contoso.chinacloudsites.cn/.auth/login/done.

      每个平台所需的代码如下:The code needed for each platform follows:

      Windows:Windows:

      private MobileServiceUser user;
      private async Task AuthenticateAsync()
      {
      
         string authority = "INSERT-AUTHORITY-HERE";
         string resourceId = "INSERT-RESOURCE-ID-HERE";
         string clientId = "INSERT-CLIENT-ID-HERE";
         string redirectUri = "INSERT-REDIRECT-URI-HERE";
         while (user == null)
         {
             string message;
             try
             {
                 AuthenticationContext ac = new AuthenticationContext(authority);
                 AuthenticationResult ar = await ac.AcquireTokenAsync(resourceId, clientId,
                     new Uri(redirectUri), new PlatformParameters(PromptBehavior.Auto, false) );
                 JObject payload = new JObject();
                 payload["access_token"] = ar.AccessToken;
                 user = await App.MobileService.LoginAsync(
                     MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload);
                 message = string.Format("You are now logged in - {0}", user.UserId);
             }
             catch (InvalidOperationException)
             {
                 message = "You must log in. Login Required";
             }
             var dialog = new MessageDialog(message);
             dialog.Commands.Add(new UICommand("OK"));
             await dialog.ShowAsync();
         }
      }
      

      Xamarin.iOSXamarin.iOS

      private MobileServiceUser user;
      private async Task AuthenticateAsync(UIViewController view)
      {
      
         string authority = "INSERT-AUTHORITY-HERE";
         string resourceId = "INSERT-RESOURCE-ID-HERE";
         string clientId = "INSERT-CLIENT-ID-HERE";
         string redirectUri = "INSERT-REDIRECT-URI-HERE";
         try
         {
             AuthenticationContext ac = new AuthenticationContext(authority);
             AuthenticationResult ar = await ac.AcquireTokenAsync(resourceId, clientId,
                 new Uri(redirectUri), new PlatformParameters(view));
             JObject payload = new JObject();
             payload["access_token"] = ar.AccessToken;
             user = await client.LoginAsync(
                 MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload);
         }
         catch (Exception ex)
         {
             Console.Error.WriteLine(@"ERROR - AUTHENTICATION FAILED {0}", ex.Message);
         }
      }
      

      Xamarin.AndroidXamarin.Android

      private MobileServiceUser user;
      private async Task AuthenticateAsync()
      {
      
         string authority = "INSERT-AUTHORITY-HERE";
         string resourceId = "INSERT-RESOURCE-ID-HERE";
         string clientId = "INSERT-CLIENT-ID-HERE";
         string redirectUri = "INSERT-REDIRECT-URI-HERE";
         try
         {
             AuthenticationContext ac = new AuthenticationContext(authority);
             AuthenticationResult ar = await ac.AcquireTokenAsync(resourceId, clientId,
                 new Uri(redirectUri), new PlatformParameters(this));
             JObject payload = new JObject();
             payload["access_token"] = ar.AccessToken;
             user = await client.LoginAsync(
                 MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload);
         }
         catch (Exception ex)
         {
             AlertDialog.Builder builder = new AlertDialog.Builder(this);
             builder.SetMessage(ex.Message);
             builder.SetTitle("You must log in. Login Required");
             builder.Create().Show();
         }
      }
      protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
      {
      
         base.OnActivityResult(requestCode, resultCode, data);
         AuthenticationAgentContinuationHelper.SetAuthenticationAgentContinuationEventArgs(requestCode, resultCode, data);
      }
      

使用来自 Microsoft 帐户的令牌进行单一登录Single Sign-On using a token from Microsoft Account

可以对 Microsoft 帐户使用此代码片段中所示的客户端流。You can use the client flow as shown in this snippet for Microsoft Account.

var token = new JObject();
// Replace access_token_value with actual value of your access token obtained
// using the Microsoft Account SDK.
token.Add("access_token", "access_token_value");

private MobileServiceUser user;
private async Task AuthenticateAsync()
{
    while (user == null)
    {
        string message;
        try
        {
            user = await client.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount, token);
            message = string.Format("You are now logged in - {0}", user.UserId);
        }
        catch (InvalidOperationException)
        {
            message = "You must log in. Login Required";
        }

        var dialog = new MessageDialog(message);
        dialog.Commands.Add(new UICommand("OK"));
        await dialog.ShowAsync();
    }
}

服务器托管的身份验证Server-managed authentication

注册标识提供者后,使用提供者的 MobileServiceAuthenticationProvider 值对 [MobileServiceClient] 调用 LoginAsync 方法。Once you have registered your identity provider, call the LoginAsync method on the [MobileServiceClient] with the MobileServiceAuthenticationProvider value of your provider. 例如,以下代码使用 Microsoft 帐户启动服务器流登录。For example, the following code initiates a server flow sign-in by using Microsoft Account.

private MobileServiceUser user;
private async System.Threading.Tasks.Task Authenticate()
{
    while (user == null)
    {
        string message;
        try
        {
            user = await client
                .LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount);
            message =
                string.Format("You are now logged in - {0}", user.UserId);
        }
        catch (InvalidOperationException)
        {
            message = "You must log in. Login Required";
        }

        var dialog = new MessageDialog(message);
        dialog.Commands.Add(new UICommand("OK"));
        await dialog.ShowAsync();
    }
}

如果使用的标识提供者不是 Microsoft 帐户,请将 MobileServiceAuthenticationProvider 的值更改为提供者的值。If you are using an identity provider other than Microsoft Account, change the value of MobileServiceAuthenticationProvider to the value for your provider.

在服务器流中,Azure 应用服务可以通过显示所选提供者的登录页来管理 OAuth 身份验证。In a server flow, Azure App Service manages the OAuth authentication flow by displaying the sign-in page of the selected provider. 标识提供者返回后,Azure 应用服务会生成一个应用服务身份验证令牌。Once the identity provider returns, Azure App Service generates an App Service authentication token. LoginAsync 方法返回 MobileServiceUser,后者提供已经过身份验证的用户的 UserId,以及 JSON Web 令牌 (JWT) 形式的 MobileServiceAuthenticationTokenThe LoginAsync method returns a MobileServiceUser, which provides both the UserId of the authenticated user and the MobileServiceAuthenticationToken, as a JSON web token (JWT). 可以缓存此令牌,并在它过期之前重复使用。This token can be cached and reused until it expires. 有关详细信息,请参阅 缓存身份验证令牌For more information, see Caching the authentication token.

缓存身份验证令牌Caching the authentication token

在某些情况下,存储提供者提供的身份验证令牌可避免在首次成功身份验证后调用登录方法。In some cases, the call to the login method can be avoided after the first successful authentication by storing the authentication token from the provider. Microsoft Store 和 UWP 应用可以使用 PasswordVault 在成功登录后缓存当前身份验证令牌,如下所示:Microsoft Store and UWP apps can use PasswordVault to cache the current authentication token after a successful sign-in, as follows:

await client.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount);      

PasswordVault vault = new PasswordVault();
vault.Add(new PasswordCredential("MicrosoftAccount", client.currentUser.UserId, 
    client.currentUser.MobileServiceAuthenticationToken));

UserId 值存储为凭据的 UserName,令牌存储为 Password。The UserId value is stored as the UserName of the credential and the token is the stored as the Password. 在后续启动时,可以检查 PasswordVault 中的缓存凭据。On subsequent start-ups, you can check the PasswordVault for cached credentials. 以下示例使用找到的缓存凭据,否则尝试再次向后端进行身份验证:The following example uses cached credentials when they are found, and otherwise attempts to authenticate again with the backend:

// Try to retrieve stored credentials.
var creds = vault.FindAllByResource("MicrosoftAccount").FirstOrDefault();
if (creds != null)
{
    // Create the current user from the stored credentials.
    client.currentUser = new MobileServiceUser(creds.UserName);
    client.currentUser.MobileServiceAuthenticationToken =
        vault.Retrieve("MicrosoftAccount", creds.UserName).Password;
}
else
{
    // Regular login flow and cache the token as shown above.
}

注销用户时,还必须删除存储的凭据,如下所示:When you sign out a user, you must also remove the stored credential, as follows:

client.Logout();
vault.Remove(vault.Retrieve("MicrosoftAccount", client.currentUser.UserId));

Xamarin 应用使用 Xamarin.Auth 将证书安全存储在 Account 对象中。Xamarin apps use the Xamarin.Auth APIs to securely store credentials in an Account object. 有关使用这些 API 的示例,请参阅 ContosoMoments 照片分享示例中的 AuthStore.cs 代码文件。For an example of using these APIs, see the AuthStore.cs code file in the ContosoMoments photo sharing sample.

使用客户端托管的身份验证时,也可以缓存从提供者(例如 MicrosoftAccount)获取的访问令牌。When you use client-managed authentication, you can also cache the access token obtained from your provider such as MicrosoftAccount. 可以提供此令牌,从后端请求新的身份验证令牌,如下所示:This token can be supplied to request a new authentication token from the backend, as follows:

var token = new JObject();
// Replace <your_access_token_value> with actual value of your access token
token.Add("access_token", "<your_access_token_value>");

// Authenticate using the access token.
await client.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount, token);

推送通知Push Notifications

以下主题介绍了推送通知:The following topics cover Push Notifications:

如何:注册推送通知How to: Register for Push Notifications

使用移动应用客户端可向 Azure 通知中心注册推送通知。The Mobile Apps client enables you to register for push notifications with Azure Notification Hubs. 注册时,你将获得从平台特定的推送通知服务 (PNS) 获取的句柄。When registering, you obtain a handle that you obtain from the platform-specific Push Notification Service (PNS). 然后用户就可以在创建注册时提供此值以及任何标记。You then provide this value along with any tags when you create the registration. 以下代码将用于推送通知的 Windows 应用注册到 Windows 通知服务 (WNS):The following code registers your Windows app for push notifications with the Windows Notification Service (WNS):

private async void InitNotificationsAsync()
{
    // Request a push notification channel.
    var channel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();

    // Register for notifications using the new channel.
    await MobileService.GetPush().RegisterNativeAsync(channel.Uri, null);
}

如果要推送到 WNS,必须获取 Microsoft Store 包 SIDIf you are pushing to WNS, then you MUST obtain a Microsoft Store package SID. 有关 Windows 应用的详细信息,包括如何注册模板,请参阅 Add push notifications to your app(将推送通知添加到应用)。For more information on Windows apps, including how to register for template registrations, see Add push notifications to your app.

不支持从客户端请求标记。Requesting tags from the client is not supported. 注册时将静默删除标记请求。Tag Requests are silently dropped from registration. 如果想要使用标记注册设备,请创建自定义 API,使用通知中心 API 自动执行注册。If you wish to register your device with tags, create a Custom API that uses the Notification Hubs API to perform the registration on your behalf. 调用自定义 API 而不是 RegisterNativeAsync() 方法。Call the Custom API instead of the RegisterNativeAsync() method.

如何:获取 Windows 应用商店包 SIDHow to: Obtain a Windows Store package SID

在 Windows 应用商店应用中启用推送通知需有包 SID。A package SID is needed for enabling push notifications in Windows Store apps. 若要接收包 SID,请向 Windows 应用商店注册应用程序。To receive a package SID, register your application with the Windows Store.

若要获取此值,请执行以下操作:To obtain this value:

  1. 在“Visual Studio 解决方案资源管理器”中,右键单击 Windows 应用商店应用项目,再单击“应用商店” > “将应用与应用商店关联...” 。In Visual Studio Solution Explorer, right-click the Windows Store app project, click Store > Associate App with the Store....
  2. 在向导中,单击“下一步” ,使用 Microsoft 帐户登录,在“保留新应用名称” 中键入应用的名称,然后单击“保留” 。In the wizard, click Next, sign in with your Microsoft account, type a name for your app in Reserve a new app name, then click Reserve.
  3. 成功创建应用注册后,选择应用名称,再依次单击“下一步” 和“关联” 。After the app registration is successfully created, select the app name, click Next, and then click Associate.
  4. 使用 Microsoft 帐户登录到 Windows 开发人员中心Log in to the Windows Dev Center using your Microsoft Account. 在“我的应用” 下面,单击创建的应用注册。Under My apps, click the app registration you created.
  5. 单击“应用管理” > “应用标识” ,然后向下滚动找到“包 SID” 。Click App management > App identity, and then scroll down to find your Package SID.

包 SID 的许多用法将其视为 URI,在这种情况下,需要使用 ms-app:// 作为方案。Many uses of the package SID treat it as a URI, in which case you need to use ms-app:// as the scheme. 记下包 SID 的版本,其中串联了此值作为前缀。Make note of the version of your package SID formed by concatenating this value as a prefix.

Xamarin 应用需要一些额外的代码才能注册 iOS 或 Android 平台上运行的应用。Xamarin apps require some additional code to be able to register an app running on the iOS or Android platforms. 有关详细信息,请参阅适用于平台的主题:For more information, see the topic for your platform:

如何:注册推送模板以发送跨平台通知How to: Register push templates to send cross-platform notifications

若要注册模板,请结合模板使用 RegisterAsync() 方法,如下所示:To register templates, use the RegisterAsync() method with the templates, as follows:

JObject templates = myTemplates();
MobileService.GetPush().RegisterAsync(channel.Uri, templates);

模板应该是 JObject 类型,并且可以包含采用以下 JSON 格式的多个模板:Your templates should be JObject types and can contain multiple templates in the following JSON format:

public JObject myTemplates()
{
    // single template for Windows Notification Service toast
    var template = "<toast><visual><binding template=\"ToastText01\"><text id=\"1\">$(message)</text></binding></visual></toast>";

    var templates = new JObject
    {
        ["generic-message"] = new JObject
        {
            ["body"] = template,
            ["headers"] = new JObject
            {
                ["X-WNS-Type"] = "wns/toast"
            },
            ["tags"] = new JArray()
        },
        ["more-templates"] = new JObject {...}
    };
    return templates;
}

方法 RegisterAsync() 也接受辅助磁贴:The method RegisterAsync() also accepts Secondary Tiles:

MobileService.GetPush().RegisterAsync(string channelUri, JObject templates, JObject secondaryTiles);

为确保安全,注册期间会移除所有标记。All tags are stripped away during registration for security. 若要将标记添加到安装或安装中的模板,请参阅[使用适用于 Azure 移动应用的 .NET 后端服务器 SDK]。To add tags to installations or templates within installations, see [Work with the .NET backend server SDK for Azure Mobile Apps].

若要使用这些注册的模板发送通知,请参阅 Notification Hubs APIs(通知中心 API)。To send notifications utilizing these registered templates, refer to the Notification Hubs APIs.

其他主题Miscellaneous Topics

如何:处理错误How to: Handle errors

后端发生错误时,客户端 SDK 会引发 MobileServiceInvalidOperationExceptionWhen an error occurs in the backend, the client SDK raises a MobileServiceInvalidOperationException. 以下示例演示如何处理后端返回的异常:The following example shows how to handle an exception that is returned by the backend:

private async void InsertTodoItem(TodoItem todoItem)
{
    // This code inserts a new TodoItem into the database. When the operation completes
    // and App Service has assigned an Id, the item is added to the CollectionView
    try
    {
        await todoTable.InsertAsync(todoItem);
        items.Add(todoItem);
    }
    catch (MobileServiceInvalidOperationException e)
    {
        // Handle error
    }
}

有关处理错误条件的其他示例,可在 Mobile Apps Files Sample(移动应用文件示例)中找到。Another example of dealing with error conditions can be found in the Mobile Apps Files Sample. LoggingHandler 示例提供日志记录委托处理程序,记录向后端发出的请求。The LoggingHandler example provides a logging delegate handler to log the requests being made to the backend.

如何:自定义请求标头How to: Customize request headers

若要支持特定的应用程序方案,可能需要自定义与移动应用后端之间的通信。To support your specific app scenario, you might need to customize communication with the Mobile App backend. 例如,可能需要将一个自定义标头添加到每个传出请求,甚至要更改响应状态代码。For example, you may want to add a custom header to every outgoing request or even change responses status codes. 可以使用自定义 DelegatingHandler 来实现此目的,如以下示例中所示:You can use a custom DelegatingHandler, as in the following example:

public async Task CallClientWithHandler()
{
    MobileServiceClient client = new MobileServiceClient("AppUrl", new MyHandler());
    IMobileServiceTable<TodoItem> todoTable = client.GetTable<TodoItem>();
    var newItem = new TodoItem { Text = "Hello world", Complete = false };
    await todoTable.InsertAsync(newItem);
}

public class MyHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage>
        SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // Change the request-side here based on the HttpRequestMessage
        request.Headers.Add("x-my-header", "my value");

        // Do the request
        var response = await base.SendAsync(request, cancellationToken);

        // Change the response-side here based on the HttpResponseMessage

        // Return the modified response
        return response;
    }
}