共用方式為


如何使用適用於 .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 對象應該是單一物件。

使用數據表

下一節將詳細說明如何搜尋和擷取記錄,以及修改數據表中的數據。 涵蓋下列主題:

建立數據表參考

存取或修改後端數據表中數據的所有程式代碼都會呼叫 MobileServiceTable 物件上的函式。 呼叫 GetTable 方法來取得數據表的參考,如下所示:

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

傳回的物件會使用具型別串行化模型。 也支援不具類型的串行化模型。 下列範例 建立不具型別數據表的參考

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

在不具類型的查詢中,您必須指定基礎 OData 查詢字串。

從行動應用程式查詢數據

本節說明如何對行動應用程式後端發出查詢,其中包含下列功能:

注意

強制執行伺服器驅動頁面大小,以防止傳回所有數據列。 分頁會保留大型數據集的預設要求,而不會影響服務。 若要傳回 50 個以上的數據列,請使用 SkipTake 方法,如 傳回頁面中的數據中所述。

篩選傳回的數據

下列程式代碼說明如何在查詢中包含 Where 子句來篩選數據。 它會傳回 todoTable 的所有專案,其 Complete 屬性等於 falseWhere 函式會將數據列篩選述詞套用至數據表的查詢。

// 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.FloorMath.Ceiling),
  • 字串函式(LengthSubstringReplaceIndexOfStartsWithEndsWith)、
  • 日期屬性(YearMonthDayHourMinuteSecond)、
  • 存取物件的屬性,以及
  • 結合上述任何作業的表達式。

考慮 Server SDK 支援的內容時,您可以考慮 OData v3 檔案

排序傳回的數據

下列程式代碼說明如何在查詢中包含 OrderByOrderByDescending 函式來排序數據。 它會從 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();

當您使用藉由呼叫 ToCollectionAsyncToCollection所建立的集合時,您會取得可系結至 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

  1. 遵循 如何設定 App Service for Active Directory 登入 教學課程,以設定行動應用程式後端Microsoft Entra 登入。 請務必完成註冊原生用戶端應用程式的選擇性步驟。

  2. 在 Visual Studio 中,開啟您的專案,並新增 Microsoft.IdentityModel.Clients.ActiveDirectory NuGet 套件的參考。 搜尋時,請包含發行前版本。

  3. 根據您使用的平臺,將下列程式代碼新增至您的應用程式。 在每個中,進行下列取代:

    • 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;
    }
}