如何使用適用於 .NET 的 Azure Mobile Apps v4.2.0 用戶端連結庫
注意
此產品已淘汰。 如需使用 .NET 8 或更新版本的專案取代專案,請參閱 Community Toolkit Datasync 連結庫。
本指南說明如何使用適用於 Azure Mobile Apps 的 .NET 用戶端連結庫來執行常見案例。 在 Windows (WPF、UWP) 或 Xamarin (Native 或 Forms) 應用程式中使用 .NET 用戶端連結庫。 如果您不熟悉 Azure Mobile Apps,請考慮先完成 Xamarin.Forms 教學課程的
警告
本文涵蓋 v4.2.0 連結庫版本的資訊,此版本已由 v5.0.0 連結庫取代。 如需最新的資訊,請參閱最新版本 文章
支援的平臺
.NET 用戶端連結庫支援 .NET Standard 2.0 和下列平臺:
- 從 API 層級 19 到 API 層級 30 的 Xamarin.Android。
- Xamarin.iOS 8.0 到 14.3 版。
- 通用 Windows 平台組建 16299 和更新版本。
- 任何 .NET Standard 2.0 應用程式。
「伺服器流程」驗證會針對呈現的UI使用WebView,而且可能無法在每個平臺上使用。 如果無法使用,您必須提供「用戶端流程」驗證。 使用驗證時,此用戶端連結庫不適合監看或IoT規格。
設定和必要條件
我們假設您已經建立併發佈 Azure Mobile Apps 後端專案,其中包含至少一個數據表。 在本主題中使用的程式代碼中,數據表會命名為 TodoItem
,而且其具有字串 Id
,以及 Text
字段和布爾值 Complete
數據行。 當您完成 快速入門時,這個數據表是建立的相同數據表。
C# 中對應的具型別用戶端類型是這個類別:
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 對應。
若要瞭解如何在 Mobile Apps 後端建立資料表,請參閱 .NET Server SDK 主題,Node.js Server SDK 主題。
安裝受控用戶端 SDK 套件
以滑鼠右鍵按下您的專案,按 [管理 NuGet 套件],搜尋 Microsoft.Azure.Mobile.Client
套件,然後按 [安裝]。 針對離線功能,也請安裝 Microsoft.Azure.Mobile.Client.SQLiteStore
套件。
建立 Azure Mobile Apps 用戶端
下列程式代碼會建立用來存取行動應用程式後端的 MobileServiceClient 物件。
var client = new MobileServiceClient("MOBILE_APP_URL");
在上述程式代碼中,將 MOBILE_APP_URL
取代為 App Service 後端的 URL。
MobileServiceClient
對象應該是單一物件。
使用數據表
下一節將詳細說明如何搜尋和擷取記錄,以及修改數據表中的數據。 涵蓋下列主題:
- 建立數據表參考
- 查詢數據
- 篩選傳回的數據
- 排序傳回的數據
- 傳回頁面中的數據
- 選取特定數據行
- 依標識碼查閱記錄
- 執行不具類型的查詢
- 插入數據
- 更新數據
- 刪除資料
- 衝突解決和開放式並行存取
- 將數據系結至 Windows 使用者介面
- 變更頁面大小
建立數據表參考
存取或修改後端數據表中數據的所有程式代碼都會呼叫 MobileServiceTable
物件上的函式。 呼叫 GetTable 方法來取得數據表的參考,如下所示:
IMobileServiceTable<TodoItem> todoTable = client.GetTable<TodoItem>();
傳回的物件會使用具型別串行化模型。 也支援不具類型的串行化模型。 下列範例 建立不具型別數據表的參考:
// Get an untyped table reference
IMobileServiceTable untypedTodoTable = client.GetTable("TodoItem");
在不具類型的查詢中,您必須指定基礎 OData 查詢字串。
從行動應用程式查詢數據
本節說明如何對行動應用程式後端發出查詢,其中包含下列功能:
注意
強制執行伺服器驅動頁面大小,以防止傳回所有數據列。 分頁會保留大型數據集的預設要求,而不會影響服務。 若要傳回 50 個以上的數據列,請使用 Skip
和 Take
方法,如 傳回頁面中的數據中所述。
篩選傳回的數據
下列程式代碼說明如何在查詢中包含 Where
子句來篩選數據。 它會傳回 todoTable
的所有專案,其 Complete
屬性等於 false
。
Where 函式會將數據列篩選述詞套用至數據表的查詢。
// This query filters out completed TodoItems and items without a timestamp.
List<TodoItem> items = await todoTable
.Where(todoItem => todoItem.Complete == false)
.ToListAsync();
您可以使用訊息檢查軟體來檢視傳送至後端的要求 URI,例如瀏覽器開發人員工具或 Fiddler。 如果您檢視要求 URI,請注意查詢字串已修改:
GET /tables/todoitem?$filter=(complete+eq+false) HTTP/1.1
此 OData 要求會由伺服器 SDK 轉譯成 SQL 查詢:
SELECT *
FROM TodoItem
WHERE ISNULL(complete, 0) = 0
傳遞至 Where
方法的函式可以有任意數目的條件。
// 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 查詢:
SELECT *
FROM TodoItem
WHERE ISNULL(complete, 0) = 0
AND ISNULL(text, 0) = 0
此查詢也可以分割成多個子句:
List<TodoItem> items = await todoTable
.Where(todoItem => todoItem.Complete == false)
.Where(todoItem => todoItem.Text != null)
.ToListAsync();
這兩種方法相等,而且可以交替使用。 在一個查詢中串連多個述詞的前選項,比較精簡且建議使用。
Where
子句支持轉換成 OData 子集的作業。 工作包括:
- 關係運算子(
==
、!=
、<
、<=
、>
、>=
)、 - 算術運算子(
+
、-
、/
、*
、%
)、 - 數字精確度 (
Math.Floor
,Math.Ceiling
), - 字串函式(
Length
、Substring
、Replace
、IndexOf
、StartsWith
、EndsWith
)、 - 日期屬性(
Year
、Month
、Day
、Hour
、Minute
、Second
)、 - 存取物件的屬性,以及
- 結合上述任何作業的表達式。
考慮 Server SDK 支援的內容時,您可以考慮 OData v3 檔案。
排序傳回的數據
下列程式代碼說明如何在查詢中包含 OrderBy 或 OrderByDescending 函式來排序數據。 它會從 Text
字段遞增排序 todoTable
傳回專案。
// 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();
傳回頁面中的數據
根據預設,後端只會傳回前 50 個數據列。 您可以呼叫 take 方法 Take
搭配 Skip 方法來要求查詢所傳回數據集總數的特定「頁面」。 執行時,下列查詢會傳回數據表中的前三個專案。
// Define a filtered query that returns the top 3 items.
MobileServiceTableQuery<TodoItem> query = todoTable.Take(3);
List<TodoItem> items = await query.ToListAsync();
下列修訂后的查詢會略過前三個結果,並傳回接下來的三個結果。 此查詢會產生數據的第二個「頁面」,其中頁面大小為三個專案。
// 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 方法會要求傳回的所有記錄 所有 總計數,忽略指定的任何分頁/限制子句:
query = query.IncludeTotalCount();
在真實世界應用程式中,您可以使用類似上述範例的查詢搭配呼叫器控件或可比較的UI,在頁面之間巡覽。
注意
若要覆寫行動應用程式後端中的 50 個資料列限制,您也必須將 EnableQueryAttribute 套用至公用 GET 方法,並指定分頁行為。 套用至 方法時,下列會將傳回的數據列上限設定為 1000:
[EnableQuery(MaxTop=1000)]
選取特定數據行
您可以將選取 子句新增至查詢,以指定要包含在結果中的屬性集。 例如,下列程式代碼示範如何只選取一個字段,以及如何選取和格式化多個字段:
// 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();
到目前為止所述的所有函式都是加總的,因此我們可以繼續鏈結它們。 每個鏈結呼叫都會影響更多查詢。 還有一個範例:
MobileServiceTableQuery<TodoItem> query = todoTable
.Where(todoItem => todoItem.Complete == false)
.Select(todoItem => todoItem.Text)
.Skip(3).
.Take(3);
List<string> items = await query.ToListAsync();
依標識碼查閱數據
LookupAsync 函式可用來查閱具有特定標識碼的資料庫物件。
// 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");
執行不具類型的查詢
使用不具類型的數據表物件執行查詢時,您必須呼叫 ReadAsync來明確指定 OData 查詢字串,如下列範例所示:
// Lookup untyped data using OData
JToken untypedItems = await untypedTodoTable.ReadAsync("$filter=complete eq 0&$orderby=text");
您可以取回 JSON 值,就像屬性包一樣。 如需 JToken
和 Newtonsoft Json 的詳細資訊,請參閱 Newtonsoft JSON 網站。
插入數據
所有用戶端類型都必須包含名為 Id的成員,預設為字串。 若要執行 CRUD 作業和離線同步處理,需要此 識別碼。下列程式代碼說明如何使用 InsertAsync 方法,將新的數據列插入數據表中。 參數包含要插入為 .NET 對象的數據。
await todoTable.InsertAsync(todoItem);
如果在插入期間未在 todoItem
中包含唯一的自定義標識碼值,則伺服器會產生 GUID。 您可以在呼叫傳回之後檢查 物件,以擷取產生的標識碼。
若要插入不具類型的數據,您可以利用 Json.NET:
JObject jo = new JObject();
jo.Add("Text", "Hello World");
jo.Add("Complete", false);
var inserted = await table.InsertAsync(jo);
以下是使用電子郵件地址作為唯一字串識別碼的範例:
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);
使用標識碼值
Mobile Apps 支援資料表 識別子 數據行的唯一自定義字串值。 字串值可讓應用程式使用自定義值,例如標識子的電子郵件地址或用戶名稱。 字串識別碼提供下列優點:
- 不會往返資料庫,就會產生標識符。
- 記錄更容易從不同的數據表或資料庫合併。
- 標識碼值可以更好地與應用程式的邏輯整合。
未在插入的記錄上設定字元串標識符值時,行動應用程式後端會產生標識符的唯一值。 您可以使用 Guid.NewGuid 方法,在用戶端或後端產生您自己的標識符值。
JObject jo = new JObject();
jo.Add("id", Guid.NewGuid().ToString("N"));
更新數據
下列程式代碼說明如何使用 UpdateAsync 方法來更新具有相同標識符的現有記錄與新資訊。 參數包含要更新為 .NET 對象的數據。
await todoTable.UpdateAsync(todoItem);
若要更新不具類型的數據,您可以利用 Newtonsoft JSON,如下所示:
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
欄位。 後端會使用 [id
] 字段來識別要更新的數據列。 您可以從 InsertAsync
呼叫的結果取得 id
欄位。 如果您嘗試更新專案而不提供 id
值,則會引發 ArgumentException
。
刪除數據
下列程式代碼說明如何使用 DeleteAsync 方法來刪除現有的實例。 實例是由在 todoItem
上設定的 id
欄位來識別。
await todoTable.DeleteAsync(todoItem);
若要刪除不具類型的數據,您可以利用 Json.NET,如下所示:
JObject jo = new JObject();
jo.Add("id", "37BBF396-11F0-4B39-85C8-B319C729AF6D");
await table.DeleteAsync(jo);
當您提出刪除要求時,必須指定標識碼。 其他屬性不會傳遞至服務,或是在服務上忽略。
DeleteAsync
呼叫的結果通常是 null
。 要傳入的標識碼可以從 InsertAsync
呼叫的結果取得。 當您嘗試刪除專案而不指定 [id
] 字段時,會擲回 MobileServiceInvalidOperationException
。
衝突解決和開放式並行存取
兩個以上的用戶端可能會同時將變更寫入相同的專案。 如果沒有衝突偵測,最後一次寫入會覆寫任何先前的更新。 開放式並行訪問控制 假設每個交易都可以認可,因此不會使用任何資源鎖定。 認可交易之前,開放式並行存取控制會確認沒有其他交易已修改數據。 如果數據已修改,則會回復認可交易。
Mobile Apps 支援開放式並行控制,方法是使用針對行動應用程式後端中每個數據表所定義的 version
系統屬性數據行來追蹤每個項目的變更。 每次更新記錄時,Mobile Apps 會將該記錄的 version
屬性設定為新的值。 在每個更新要求期間,與伺服器上記錄的相同屬性進行比較時,要求所包含的記錄 version
屬性。 如果以要求傳遞的版本不符合後端,則用戶端連結庫會引發 MobileServicePreconditionFailedException<T>
例外狀況。 例外狀況所包含的類型是來自後端的記錄,其中包含記錄的伺服器版本。 然後,應用程式可以使用這項資訊來決定是否使用後端的正確 version
值再次執行更新要求,以認可變更。
為 version
系統屬性定義資料表類別上的數據行,以啟用開放式並行存取。 例如:
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
旗標,如下所示。
//Enable optimistic concurrency by retrieving version
todoTable.SystemProperties |= MobileServiceSystemProperties.Version;
除了啟用開放式並行存取之外,您也必須在呼叫 updateAsync 時,攔截程式代碼中的 MobileServicePreconditionFailedException<T>
例外狀況。 將正確的 version
套用至更新的記錄,並使用已解決的記錄呼叫 updateAsync,以解決衝突。 下列程式代碼示範如何解決偵測到的寫入衝突:
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();
}
如需詳細資訊,請參閱 Azure Mobile Apps 中的 離機數據同步 主題。
將數據系結至 Windows 用戶介面
本節說明如何使用 Windows 應用程式中的 UI 元素來顯示傳回的數據物件。 下列範例程式代碼會系結至清單的來源,並查詢不完整的專案。 MobileServiceCollection 會建立 Mobile Apps 感知系結集合。
// 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;
Managed 運行時間中的某些控件支援名為 ISupportIncrementalLoading的介面。 此介面可讓控件在用戶捲動時要求額外的數據。 透過 MobileServiceIncrementalLoadingCollection為通用 Windows 應用程式提供此介面的內建支援,這會自動處理控件的呼叫。 在 Windows 應用程式中使用 MobileServiceIncrementalLoadingCollection
,如下所示:
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
擴充方法。 若要載入資料,請呼叫 LoadMoreItemsAsync()
。
MobileServiceCollection<TodoItem, TodoItem> items = todoTable.Where(todoItem => todoItem.Complete==false).ToCollection();
await items.LoadMoreItemsAsync();
當您使用藉由呼叫 ToCollectionAsync
或 ToCollection
所建立的集合時,您會取得可系結至 UI 控件的集合。 此集合是分頁感知。 由於集合正在從網路載入數據,因此載入有時會失敗。 若要處理這類失敗,請覆寫 MobileServiceIncrementalLoadingCollection
上的 OnException
方法,以處理呼叫 LoadMoreItemsAsync
所產生的例外狀況。
請考慮您的數據表是否有許多欄位,但您只想在控件中顯示其中一些字段。 您可以使用上一節中的指引「選取特定數據行」,以選取 UI 中顯示的特定數據行。
變更頁面大小
根據預設,Azure Mobile Apps 會傳回每個要求最多 50 個專案。 您可以藉由增加客戶端和伺服器上的頁面大小上限來變更分頁大小。 若要增加所要求的頁面大小,請在使用 PullAsync()
時指定 PullOptions
:
PullOptions pullOptions = new PullOptions
{
MaxPageSize = 100
};
假設您已在伺服器內將 PageSize
等於或大於 100,要求最多會傳回 100 個專案。
使用離線數據表
離線數據表會使用本機 SQLite 存放區來儲存數據,以供離線使用。 所有數據表作業都會針對本機 SQLite 存放區完成,而不是遠端伺服器存放區。 若要建立離線數據表,請先準備您的專案。
- 在 Visual Studio 中,以滑鼠右鍵按兩下方案 >管理方案的 NuGet 套件...,然後搜尋並安裝解決方案中所有專案的 Microsoft.Azure.Mobile.Client.SQLiteStore NuGet 套件。
- 針對 Windows 裝置,按
[參考] [新增參考...] ,展開Windows 資料夾,然後為 Windows SDK 啟用適當的 SQLite,以及適用於 Windows SDK 的 Visual C++ 2013 Runtime。 SQLite SDK 名稱會隨著每個 Windows 平臺而稍有不同。
必須先備妥本地存儲,才能建立數據表參考:
var store = new MobileServiceSQLiteStore(Constants.OfflineDbPath);
store.DefineTable<TodoItem>();
//Initializes the SyncContext using the default IMobileServiceSyncHandler.
await this.client.SyncContext.InitializeAsync(store);
建立客戶端之後,通常會立即完成存放區初始化。 OfflineDbPath 應該是適合在您支援的所有平臺上使用的檔名。 如果路徑是完整路徑(也就是以斜線開頭),則會使用該路徑。 如果路徑未完整,檔案會放在平臺特定位置。
- 針對 iOS 和 Android 裝置,預設路徑為 「個人檔案」資料夾。
- 針對 Windows 裝置,預設路徑是應用程式特定的 「AppData」 資料夾。
您可以使用 GetSyncTable<>
方法來取得資料表參考:
var table = client.GetSyncTable<TodoItem>();
您不需要驗證即可使用離線數據表。 您只需要在與後端服務通訊時進行驗證。
同步處理離線數據表
離線資料表預設不會與後端同步處理。 同步處理分成兩個部分。 您可以將變更與下載新專案分開推送。 以下是典型的同步處理方法:
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,則不會使用累加同步處理。 每個同步作業都會擷取所有記錄。
SDK 會在提取記錄之前執行隱含 PushAsync()
。
衝突處理發生在 PullAsync()
方法上。 您可以使用與線上數據表相同的方式處理衝突。 呼叫 PullAsync()
,而不是在插入、更新或刪除期間產生衝突。 如果發生多個衝突,它們會組合成單一 MobileServicePushFailedException。 分別處理每個失敗。
使用自定義 API
自定義 API 可讓您定義自訂端點,以公開不會對應至插入、更新、刪除或讀取作業的伺服器功能。 藉由使用自定義 API,您可以更充分掌控傳訊,包括讀取和設定 HTTP 訊息標頭,以及定義 JSON 以外的訊息本文格式。
您可以在用戶端上呼叫其中一個 InvokeApiAsync 方法來呼叫自定義 API。 例如,下列程式代碼行會將POST要求傳送至後端的 completeAll API:
var result = await client.InvokeApiAsync<MarkAllResult>("completeAll", System.Net.Http.HttpMethod.Post, null);
此窗體是具型別的方法呼叫,而且需要定義 MarkAllResult 傳回型別。 支援具型別和不具型別的方法。
除非 API 以 『/』 開頭,否則 InvokeApiAsync() 方法會將 '/api/' 前面加上您想要呼叫的 API。 例如:
-
InvokeApiAsync("completeAll",...)
在後端呼叫 /api/completeAll -
InvokeApiAsync("/.auth/me",...)
在後端呼叫 /.auth/me
您可以使用 InvokeApiAsync 來呼叫任何 WebAPI,包括未使用 Azure Mobile Apps 定義的 Web API。 當您使用 InvokeApiAsync()時,適當的標頭,包括驗證標頭,會隨要求一起傳送。
驗證使用者
Mobile Apps 支援使用各種外部身分識別提供者來驗證和授權應用程式使用者:Facebook、Google、Microsoft 帳戶、Twitter 和Microsoft Entra ID。 您可以設定資料表的許可權,將特定作業的存取限制為僅限已驗證的使用者。 您也可以使用已驗證使用者的身分識別,在伺服器腳本中實作授權規則。
支援兩個驗證流程:用戶端管理 和 伺服器管理的 流程。 伺服器管理的流程提供最簡單的驗證體驗,因為它依賴提供者的 Web 驗證介面。 用戶端管理的流程可讓您更深入地整合裝置特定功能,因為它依賴提供者特定的裝置特定 SDK。
注意
建議您在生產應用程式中使用用戶端管理的流程。
若要設定驗證,您必須向一或多個識別提供者註冊您的應用程式。 識別提供者會產生應用程式的用戶端標識碼和客戶端密碼。 然後在後端中設定這些值,以啟用 Azure App Service 驗證/授權。
本節涵蓋下列主題:
用戶端管理的驗證
您的應用程式可以獨立連絡識別提供者,然後在使用後端登入期間提供傳回的令牌。 此用戶端流程可讓您為使用者提供單一登錄體驗,或從識別提供者擷取額外的用戶數據。 用戶端流程驗證偏好使用伺服器流程,因為身分識別提供者 SDK 提供更原生的 UX 風格,並允許進行更多自定義。
提供下列用戶端流程驗證模式的範例:
使用 Active Directory 驗證連結庫驗證使用者
您可以使用 Active Directory 驗證連結庫 (ADAL) 從用戶端使用 Microsoft Entra 驗證起始使用者驗證。
警告
Active Directory 驗證連結庫的支援將於 2022 年 12 月結束。 在現有操作系統版本上使用ADAL的應用程式將繼續運作,但技術支援和安全性更新將會結束。 如需詳細資訊,請參閱 將應用程式遷移至 MSAL。
遵循 如何設定 App Service for Active Directory 登入 教學課程,以設定行動應用程式後端Microsoft Entra 登入。 請務必完成註冊原生用戶端應用程式的選擇性步驟。
在 Visual Studio 中,開啟您的專案,並新增
Microsoft.IdentityModel.Clients.ActiveDirectory
NuGet 套件的參考。 搜尋時,請包含發行前版本。根據您使用的平臺,將下列程式代碼新增至您的應用程式。 在每個中,進行下列取代:
將 INSERT-AUTHORITY-HERE 取代為您布建應用程式的租用戶名稱。 格式應
https://login.microsoftonline.com/contoso.onmicrosoft.com
。 此值可以從 [Azure 入口網站] 中Microsoft Entra ID 中的 [網域] 索引標籤複製。以行動應用程式後端的用戶端標識碼取代 INSERT-RESOURCE-ID-HERE。 您可以從入口網站中 [Microsoft Entra Settings] 底下的 [進階] 索引標籤取得用戶端識別符。
將 INSERT-CLIENT-ID-HERE 取代為您從原生用戶端應用程式複製的用戶端識別碼。
使用 HTTPS 配置,將 INSERT-REDIRECT-URI-HERE 取代為網站的
/.auth/login/done
端點。 此值應該類似於https://contoso.azurewebsites.net/.auth/login/done
。每個平臺所需的程式代碼如下:
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.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.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); }
使用 Facebook 或 Google 令牌的單一登錄
您可以使用用戶端流程,如此適用於Facebook或Google的代碼段所示。
var token = new JObject();
// Replace access_token_value with actual value of your access token obtained
// using the Facebook or Google SDK.
token.Add("access_token", "access_token_value");
private MobileServiceUser user;
private async Task AuthenticateAsync()
{
while (user == null)
{
string message;
try
{
// Change MobileServiceAuthenticationProvider.Facebook
// to MobileServiceAuthenticationProvider.Google if using Google auth.
user = await client.LoginAsync(MobileServiceAuthenticationProvider.Facebook, 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();
}
}
伺服器管理的驗證
註冊識別提供者之後,請使用提供者的 MobileServiceAuthenticationProvider 值呼叫 MobileServiceClient 上的 LoginAsync 方法。 例如,下列程式代碼會使用Facebook起始伺服器流程登入。
private MobileServiceUser user;
private async System.Threading.Tasks.Task Authenticate()
{
while (user == null)
{
string message;
try
{
user = await client
.LoginAsync(MobileServiceAuthenticationProvider.Facebook);
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();
}
}
如果您使用 Facebook 以外的識別提供者,請將 MobileServiceAuthenticationProvider 的值變更為提供者的值。
在伺服器流程中,Azure App Service 會藉由顯示所選提供者的登入頁面來管理 OAuth 驗證流程。 識別提供者傳回后,Azure App Service 會產生 App Service 驗證令牌。 LoginAsync 方法會傳回 MobileServiceUser,它同時提供已驗證使用者的 UserId 和 MobileServiceAuthenticationToken 作為 JSON Web 令牌 (JWT)。 此令牌可以快取並重複使用,直到到期為止。 如需詳細資訊,請參閱 快取驗證權杖。
注意
實際上,Azure Mobile Apps 會使用 Xamarin.Essentials WebAuthenticator 來執行這項工作。 您必須呼叫回 Xamarin.Essentials 來處理來自服務的回應。 如需詳細資訊,請參閱 WebAuthenticator。
快取驗證令牌
在某些情況下,藉由從提供者儲存驗證令牌,即可避免在第一次成功驗證之後呼叫登入方法。 Microsoft Store 和 UWP 應用程式可以使用 PasswordVault,在成功登入之後快取目前的驗證令牌,如下所示:
await client.LoginAsync(MobileServiceAuthenticationProvider.Facebook);
PasswordVault vault = new PasswordVault();
vault.Add(new PasswordCredential("Facebook", client.currentUser.UserId,
client.currentUser.MobileServiceAuthenticationToken));
UserId 值會儲存為認證的 UserName,而令牌會儲存為密碼。 在後續的啟動時,您可以檢查 PasswordVault 是否有快取的認證。 下列範例會在找到快取認證時使用快取認證,否則會嘗試再次向後端進行驗證:
// Try to retrieve stored credentials.
var creds = vault.FindAllByResource("Facebook").FirstOrDefault();
if (creds != null)
{
// Create the current user from the stored credentials.
client.currentUser = new MobileServiceUser(creds.UserName);
client.currentUser.MobileServiceAuthenticationToken =
vault.Retrieve("Facebook", creds.UserName).Password;
}
else
{
// Regular login flow and cache the token as shown above.
}
當您註銷使用者時,也必須移除預存認證,如下所示:
client.Logout();
vault.Remove(vault.Retrieve("Facebook", client.currentUser.UserId));
當您使用用戶端管理的驗證時,您也可以快取從您的提供者取得的存取令牌,例如 Facebook 或 Twitter。 您可以提供此權杖以向後端要求新的驗證權杖,如下所示:
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.Facebook, token);
其他主題
處理錯誤
當後端發生錯誤時,用戶端 SDK 會引發 MobileServiceInvalidOperationException
。 下列範例示範如何處理後端傳回的例外狀況:
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
}
}
自定義要求標頭
若要支援您的特定應用程式案例,您可能需要自訂與行動應用程式後端的通訊。 例如,您可能想要將自定義標頭新增至每個傳出要求,甚至變更響應狀態代碼。 您可以使用自定義 DelegatingHandler,如下列範例所示:
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;
}
}
啟用要求記錄
您也可以使用 DelegatingHandler 來新增要求記錄:
public class LoggingHandler : DelegatingHandler
{
public LoggingHandler() : base() { }
public LoggingHandler(HttpMessageHandler innerHandler) : base(innerHandler) { }
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken token)
{
Debug.WriteLine($"[HTTP] >>> {request.Method} {request.RequestUri}");
if (request.Content != null)
{
Debug.WriteLine($"[HTTP] >>> {await request.Content.ReadAsStringAsync().ConfigureAwait(false)}");
}
HttpResponseMessage response = await base.SendAsync(request, token).ConfigureAwait(false);
Debug.WriteLine($"[HTTP] <<< {response.StatusCode} {response.ReasonPhrase}");
if (response.Content != null)
{
Debug.WriteLine($"[HTTP] <<< {await response.Content.ReadAsStringAsync().ConfigureAwait(false)}");
}
return response;
}
}