共用方式為


如何使用適用於 .NET 的 Azure Mobile Apps 用戶端連結庫

注意

此產品已淘汰。 如需使用 .NET 8 或更新版本的專案取代專案,請參閱 Community Toolkit Datasync 連結庫

本指南說明如何使用適用於 Azure Mobile Apps 的 .NET 用戶端連結庫來執行常見案例。 在任何 .NET 6 或 .NET Standard 2.0 應用程式中使用 .NET 用戶端連結庫,包括 MAUI、Xamarin 和 Windows (WPF、UWP 和 WinUI)。

如果您不熟悉 Azure Mobile Apps,請考慮先完成其中一個快速入門教學課程:

注意

本文涵蓋 Microsoft Datasync Framework 的最新版 (v6.0) 版本。 針對較舊的用戶端,請參閱 v4.2.0 檔案

支援的平臺

.NET 用戶端連結庫支援任何 .NET Standard 2.0 或 .NET 6 平臺,包括:

  • 適用於 Android、iOS 和 Windows 平臺的 .NET MAUI。
  • Android API 層級 21 和更新版本 (Xamarin 和 Android for .NET)。
  • iOS 12.0 版和更新版本(適用於 .NET 的 Xamarin 和 iOS)。
  • 通用 Windows 平臺會建置 19041 和更新版本。
  • Windows Presentation Framework (WPF)。
  • Windows App SDK (WinUI 3)。
  • Xamarin.Forms

此外,已針對Avalonia 和 uno Platform建立 範例。 TodoApp 範例 包含每個測試平臺的範例。

設定和必要條件

從 NuGet 新增下列連結庫:

如果使用平台專案(例如 .NET MAUI),請確定您將連結庫新增至平台專案和任何共享專案。

建立服務用戶端

下列程式代碼會建立服務用戶端,用來協調與後端和離線數據表的所有通訊。

var options = new DatasyncClientOptions 
{
    // Options set here
};
var client = new DatasyncClient("MOBILE_APP_URL", options);

在上述程式代碼中,將 MOBILE_APP_URL 取代為 ASP.NET Core 後端的 URL。 用戶端應建立為單一用戶端。 如果使用驗證提供者,則可以如下所示進行設定:

var options = new DatasyncClientOptions 
{
    // Options set here
};
var client = new DatasyncClient("MOBILE_APP_URL", authProvider, options);

本檔稍後會提供驗證提供者的詳細數據。

選項

您可以建立一組完整的 (預設值) 選項,如下所示:

var options = new DatasyncClientOptions
{
    HttpPipeline = new HttpMessageHandler[](),
    IdGenerator = (table) => Guid.NewGuid().ToString("N"),
    InstallationId = null,
    OfflineStore = null,
    ParallelOperations = 1,
    SerializerSettings = null,
    TableEndpointResolver = (table) => $"/tables/{tableName.ToLowerInvariant()}",
    UserAgent = $"Datasync/5.0 (/* Device information */)"
};

HttpPipeline

一般而言,HTTP 要求是透過驗證提供者傳遞要求來提出(這會在傳送要求之前新增目前已驗證使用者的 Authorization 標頭)。 您可以選擇性地新增更多委派處理程式。 每個要求都會先通過委派處理程式,再傳送至服務。 委派處理程式可讓您新增額外的標頭、執行重試或提供記錄功能。

本文稍後會提供委派處理程式的範例,以便 記錄新增要求 標頭。

IdGenerator

將實體新增至離線數據表時,它必須具有標識碼。 如果未提供標識碼,就會產生標識符。 [IdGenerator] 選項可讓您量身打造產生的標識符。 根據預設,會產生全域唯一標識符。 例如,下列設定會產生包含資料表名稱和 GUID 的字串:

var options = new DatasyncClientOptions 
{
    IdGenerator = (table) => $"{table}-{Guid.NewGuid().ToString("D").ToUpperInvariant()}"
}

InstallationId

如果已設定 InstallationId,就會傳送自定義標頭 X-ZUMO-INSTALLATION-ID,並傳送每個要求來識別特定裝置上的應用程式組合。 此標頭可以在記錄中記錄,並可讓您判斷應用程式的相異安裝數目。 如果您使用 InstallationId,則標識符應該儲存在裝置上的永續性記憶體中,以便追蹤唯一的安裝。

OfflineStore

設定離線數據存取時,會使用 OfflineStore。 如需詳細資訊,請參閱 使用離線資料表

ParallelOperations

離線同步處理程式的一部分牽涉到將佇列作業推送至遠端伺服器。 觸發推送作業時,作業會依收到的順序提交。 您可以選擇性地使用最多八個線程來推送這些作業。 平行作業會在客戶端和伺服器上使用更多資源,更快速地完成作業。 使用多個線程時,無法保證作業到達伺服器的順序。

SerializerSettings

如果您已變更數據同步伺服器上的串行化程式設定,則必須對用戶端上的 SerializerSettings 進行相同的變更。 這個選項可讓您指定自己的串行化程式設定。

TableEndpointResolver

依照慣例,數據表位於位於 /tables/{tableName} 路徑的遠端服務上(如伺服器程式代碼中的 Route 屬性所指定)。 不過,數據表可以存在於任何端點路徑。 TableEndpointResolver 是一個函式,可將數據表名稱轉換成與遠端服務通訊的路徑。

例如,下列會變更假設,讓所有數據表都位於 /api底下:

var options = new DatasyncClientOptions
{
    TableEndpointResolver = (table) => $"/api/{table}"
};

UserAgent

數據同步客戶端會根據連結庫的版本產生適當的 User-Agent 標頭值。 有些開發人員覺得使用者代理程序標頭會洩漏客戶端的相關信息。 您可以將 UserAgent 屬性設定為任何有效的標頭值。

使用遠端數據表

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

建立遠端數據表參考

若要建立遠端資料表參考,請使用 GetRemoteTable<T>

IRemoteTable<TodoItem> remoteTable = client.GetRemoteTable();

如果您要傳回唯讀資料表,請使用 IReadOnlyRemoteTable<T> 版本:

IReadOnlyRemoteTable<TodoItem> remoteTable = client.GetRemoteTable();

模型類型必須實作服務 ITableData 合約。 使用 DatasyncClientData 提供必要欄位:

public class TodoItem : DatasyncClientData
{
    public string Title { get; set; }
    public bool IsComplete { get; set; }
}

DatasyncClientData 物件包括:

  • Id (string) - 專案的全域唯一標識符。
  • UpdatedAt (System.DataTimeOffset) - 專案上次更新的日期/時間。
  • Version (string) - 用於版本設定的不透明字串。
  • Deleted (布林值) - 如果 true,則會刪除專案。

服務會維護這些欄位。 請勿將這些欄位調整為用戶端應用程式的一部分。

您可以使用Newtonsoft.JSON 屬性來標註模型。 您可以使用 DataTable 屬性來指定資料表的名稱:

[DataTable("todoitem")]
public class MyTodoItemClass : DatasyncClientData
{
    public string Title { get; set; }
    public bool IsComplete { get; set; }
}

或者,在 GetRemoteTable() 呼叫中指定資料表的名稱:

IRemoteTable<TodoItem> remoteTable = client.GetRemoteTable("todoitem");

用戶端會使用路徑 /tables/{tablename} 作為URI。 數據表名稱也是 SQLite 資料庫中離線資料表的名稱。

支援的類型

除了基本類型 (int、float、string 等),模型也支援下列類型:

  • System.DateTime - 以 ISO-8601 UTC 日期/時間字串作為 ms 精確度。
  • System.DateTimeOffset - 以 ISO-8601 UTC 日期/時間字串作為 ms 精確度。
  • System.Guid - 格式化為以連字元分隔的 32 位數。

從遠端伺服器查詢數據

遠端資料表可以與類似 LINQ 的語句搭配使用,包括:

  • 使用 .Where() 子句進行篩選。
  • 使用各種 .OrderBy() 子句排序。
  • 選取具有 .Select()的屬性。
  • 使用 .Skip().Take()進行分頁。

計算查詢中的專案

如果您需要查詢傳回的項目計數,您可以在數據表上使用 .CountItemsAsync(),或在查詢上使用 .LongCountAsync()

// Count items in a table.
long count = await remoteTable.CountItemsAsync();

// Count items in a query.
long count = await remoteTable.Where(m => m.Rating == "R").LongCountAsync();

這個方法會導致伺服器的來回行程。 您也可以在填入清單時取得計數(例如),避免額外的來回行程:

var enumerable = remoteTable.ToAsyncEnumerable() as AsyncPageable<T>;
var list = new List<T>();
long count = 0;
await foreach (var item in enumerable)
{
    count = enumerable.Count;
    list.Add(item);
}

計數會在第一個擷取數據表內容的要求之後填入。

傳回所有數據

數據會透過 IAsyncEnumerable傳回:

var enumerable = remoteTable.ToAsyncEnumerable();
await foreach (var item in enumerable) 
{
    // Process each item
}

使用下列任一終止子句,將 IAsyncEnumerable<T> 轉換成不同的集合:

T[] items = await remoteTable.ToArrayAsync();

Dictionary<string, T> items = await remoteTable.ToDictionaryAsync(t => t.Id);

HashSet<T> items = await remoteTable.ToHashSetAsync();

List<T> items = await remoteTable.ToListAsync();

在幕後,遠端數據表會為您處理結果的分頁。 不論需要多少伺服器端要求才能完成查詢,都會傳回所有專案。 這些元素也適用於查詢結果(例如,remoteTable.Where(m => m.Rating == "R"))。

數據同步架構也會提供 ConcurrentObservableCollection<T> - 安全線程的可觀察集合。 這個類別可用於通常使用 ObservableCollection<T> 來管理清單的 UI 應用程式內容(例如 Xamarin Forms 或 MAUI 清單)。 您可以直接從資料表或查詢清除與載入 ConcurrentObservableCollection<T>

var collection = new ConcurrentObservableCollection<T>();
await remoteTable.ToObservableCollection(collection);

使用 .ToObservableCollection(collection) 會針對整個集合觸發 CollectionChanged 事件一次,而不是針對個別專案觸發事件,因而產生較快的重繪時間。

ConcurrentObservableCollection<T> 也有述詞驅動修改:

// Add an item only if the identified item is missing.
bool modified = collection.AddIfMissing(t => t.Id == item.Id, item);

// Delete one or more item(s) based on a predicate
bool modified = collection.DeleteIf(t => t.Id == item.Id);

// Replace one or more item(s) based on a predicate
bool modified = collection.ReplaceIf(t => t.Id == item.Id, item);

在事先不知道專案的索引時,可以在事件處理程式中使用述詞驅動修改。

篩選數據

您可以使用 .Where() 子句來篩選數據。 例如:

var items = await remoteTable.Where(x => !x.IsComplete).ToListAsync();

篩選會在 IAsyncEnumerable 之前在服務上完成,並在 IAsyncEnumerable 之後在用戶端上完成。 例如:

var items = (await remoteTable.Where(x => !x.IsComplete).ToListAsync()).Where(x => x.Title.StartsWith("The"));

第一個 .Where() 子句(只傳回不完整的專案)會在服務上執行,而第二個 .Where() 子句(從 “The” 開始)則會在用戶端上執行。

Where 子句支持轉換成 OData 子集的作業。 工作包括:

  • 關係運算子(==!=<<=>>=)、
  • 算術運算子(+-/*%)、
  • 數字精確度 (Math.FloorMath.Ceiling),
  • 字串函式(LengthSubstringReplaceIndexOfEqualsStartsWithEndsWith)(僅限序數和不變的文化特性),
  • 日期屬性(YearMonthDayHourMinuteSecond)、
  • 存取物件的屬性,以及
  • 結合上述任何作業的表達式。

排序數據

搭配屬性存取子使用 .OrderBy().OrderByDescending().ThenBy().ThenByDescending() 來排序數據。

var items = await remoteTable.OrderBy(x => x.IsComplete).ThenBy(x => x.Title).ToListAsync();

排序是由服務完成。 您無法在任何排序子句中指定表示式。 如果您要依表示式排序,請使用用戶端排序:

var items = await remoteTable.ToListAsync().OrderBy(x => x.Title.ToLowerCase());

選取屬性

您可以從服務傳回資料子集:

var items = await remoteTable.Select(x => new { x.Id, x.Title, x.IsComplete }).ToListAsync();

傳回數據頁面

您可以使用 .Skip().Take() 來實作分頁,傳回數據集的子集:

var pageOfItems = await remoteTable.Skip(100).Take(10).ToListAsync();

在真實世界應用程式中,您可以使用類似上述範例的查詢搭配呼叫器控件或可比較的UI,在頁面之間巡覽。

到目前為止所述的所有函式都是加總的,因此我們可以繼續鏈結它們。 每個鏈結呼叫都會影響更多查詢。 還有一個範例:

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

依標識碼查閱遠程數據

GetItemAsync 函式可用來查閱具有特定標識符的資料庫物件。

TodoItem item = await remoteTable.GetItemAsync("37BBF396-11F0-4B39-85C8-B319C729AF6D");

如果您嘗試擷取的項目已經過虛刪除,您必須使用 includeDeleted 參數:

// The following code will throw a DatasyncClientException if the item is soft-deleted.
TodoItem item = await remoteTable.GetItemAsync("37BBF396-11F0-4B39-85C8-B319C729AF6D");

// This code will retrieve the item even if soft-deleted.
TodoItem item = await remoteTable.GetItemAsync("37BBF396-11F0-4B39-85C8-B319C729AF6D", includeDeleted: true);

在遠端伺服器上插入數據

所有用戶端類型都必須包含名為 Id的成員,預設為字串。 若要執行 CRUD 作業和離線同步處理,需要此 識別碼。下列程式代碼說明如何使用 InsertItemAsync 方法,將新的數據列插入數據表中。 參數包含要插入為 .NET 對象的數據。

var item = new TodoItem { Title = "Text", IsComplete = false };
await remoteTable.InsertItemAsync(item);
// Note that item.Id will now be set

如果在插入期間 item 未包含唯一的自定義標識碼值,則伺服器會產生標識碼。 您可以在呼叫傳回之後檢查 物件,以擷取產生的標識碼。

更新遠端伺服器上的數據

下列程式代碼說明如何使用 ReplaceItemAsync 方法來更新具有相同標識符的現有記錄與新資訊。

// In this example, we assume the item has been created from the InsertItemAsync sample

item.IsComplete = true;
await remoteTable.ReplaceItemAsync(todoItem);

刪除遠端伺服器上的數據

下列程式代碼說明如何使用 DeleteItemAsync 方法來刪除現有的實例。

// In this example, we assume the item has been created from the InsertItemAsync sample

await todoTable.DeleteItemAsync(item);

衝突解決和開放式並行存取

兩個以上的用戶端可以同時將變更寫入相同的專案。 如果沒有衝突偵測,最後一次寫入會覆寫任何先前的更新。 開放式並行訪問控制 假設每個交易都可以認可,因此不會使用任何資源鎖定。 開放式並行控制會先確認沒有其他交易在認可數據之前修改過數據。 如果數據已修改,則會回復交易。

Azure Mobile Apps 支援開放式並行控制,方法是使用行動應用程式後端中每個數據表所定義的 version 系統屬性數據行來追蹤每個項目的變更。 每次更新記錄時,Mobile Apps 會將該記錄的 version 屬性設定為新的值。 在每個更新要求期間,與伺服器上記錄的相同屬性進行比較時,要求所包含的記錄 version 屬性。 如果以要求傳遞的版本不符合後端,則用戶端連結庫會引發 DatasyncConflictException<T> 例外狀況。 例外狀況所包含的類型是來自後端的記錄,其中包含記錄的伺服器版本。 然後,應用程式可以使用這項資訊來決定是否使用後端的正確 version 值再次執行更新要求,以認可變更。

使用 DatasyncClientData 基底物件時,會自動啟用開放式並行存取。

除了啟用開放式並行存取之外,您也必須在程式代碼中攔截 DatasyncConflictException<T> 例外狀況。 將正確的 version 套用至更新的記錄,然後使用已解決的記錄重複呼叫,以解決衝突。 下列程式代碼示範如何解決偵測到的寫入衝突:

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

    try
    {
        //update at the remote table
        await remoteTable.UpdateAsync(item);
    }
    catch (DatasyncConflictException<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();
}

使用離線數據表

離線數據表會使用本機 SQLite 存放區來儲存數據,以供離線使用。 所有數據表作業都會針對本機 SQLite 存放區完成,而不是遠端伺服器存放區。 請確定您將 Microsoft.Datasync.Client.SQLiteStore 新增至每個平台專案,以及任何共享專案。

必須先備妥本地存儲,才能建立數據表參考:

var store = new OfflineSQLiteStore(Constants.OfflineConnectionString);
store.DefineTable<TodoItem>();

定義存放區之後,您可以建立用戶端:

var options = new DatasyncClientOptions 
{
    OfflineStore = store
};
var client = new DatasyncClient("MOBILE_URL", options);

最後,您必須確定離線功能已初始化:

await client.InitializeOfflineStoreAsync();

建立客戶端之後,通常會立即完成存放區初始化。 OfflineConnectionString 是用來指定 SQLite 資料庫位置和用來開啟資料庫的選項的 URI。 如需詳細資訊,請參閱 SQLiteURI 檔名。

  • 若要使用記憶體內部快取,請使用 file:inmemory.db?mode=memory&cache=private
  • 若要使用檔案,請使用 file:/path/to/file.db

您必須指定檔案的絕對檔名。 如果使用 Xamarin,您可以使用 Xamarin Essentials 檔系統協助程式 來建構路徑:例如:

var dbPath = $"{Filesystem.AppDataDirectory}/todoitems.db";
var store = new OfflineSQLiteStore($"file:/{dbPath}?mode=rwc");

如果您使用 MAUI,您可以使用 MAUI 檔案系統協助程式 來建構路徑:例如:

var dbPath = $"{Filesystem.AppDataDirectory}/todoitems.db";
var store = new OfflineSQLiteStore($"file:/{dbPath}?mode=rwc");

建立離線數據表

您可以使用 GetOfflineTable<T> 方法來取得資料表參考:

IOfflineTable<TodoItem> table = client.GetOfflineTable<TodoItem>();

如同遠端資料表,您也可以公開唯讀離線數據表:

IReadOnlyOfflineTable<TodoItem> table = client.GetOfflineTable<TodoItem>();

您不需要驗證即可使用離線數據表。 您只需要在與後端服務通訊時進行驗證。

同步處理離線數據表

離線資料表預設不會與後端同步處理。 同步處理分成兩個部分。 您可以將變更與下載新專案分開推送。 例如:

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

    try
    {
        foreach (var offlineTable in offlineTables.Values)
        {
            await offlineTable.PushItemsAsync();
            await offlineTable.PullItemsAsync("", options);
        }
    }
    catch (PushFailedException exc)
    {
        if (exc.PushResult != null)
        {
            syncErrors = exc.PushResult.Errors;
        }
    }

    // Simple error/conflict handling
    if (syncErrors != null)
    {
        foreach (var error in syncErrors)
        {
            if (error.OperationKind == TableOperationKind.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"]);
        }
    }
}

根據預設,所有數據表都會使用累加同步處理 - 只會擷取新的記錄。 每個唯一查詢都會包含一筆記錄(藉由建立 OData 查詢的 MD5 哈希所產生)。

注意

PullItemsAsync 的第一個自變數是 OData 查詢,指出要提取到裝置的記錄。 最好修改服務,只傳回使用者特定的記錄,而不是在用戶端上建立複雜的查詢。

選項(由 PullOptions 物件定義)通常不需要設定。 選項包括:

  • PushOtherTables - 如果設定為 true,則會推送所有數據表。
  • QueryId - 要使用的特定查詢識別碼,而不是產生的查詢標識碼。
  • WriteDeltaTokenInterval - 寫入用來追蹤增量同步處理的差異令牌的頻率。

SDK 會在提取記錄之前執行隱含 PushAsync()

衝突處理發生在 PullAsync() 方法上。 以與在線數據表相同的方式處理衝突。 呼叫 PullAsync(),而不是在插入、更新或刪除期間產生衝突。 如果發生多個衝突,它們會組合成單一 PushFailedException。 分別處理每個失敗。

推送所有數據表的變更

若要將所有變更推送至遠端伺服器,請使用:

await client.PushTablesAsync();

若要推送數據表子集的變更,請為 PushTablesAsync() 方法提供 IEnumerable<string>

var tablesToPush = new string[] { "TodoItem", "Notes" };
await client.PushTables(tablesToPush);

使用 client.PendingOperations 屬性來讀取等候推送至遠端服務的作業數目。 未設定離線存放區時,此屬性 null

執行複雜的 SQLite 查詢

如果您需要對離線資料庫執行複雜的 SQL 查詢,您可以使用 ExecuteQueryAsync() 方法來執行此動作。 例如,若要執行 SQL JOIN 語句,請定義顯示傳回值結構的 JObject,然後使用 ExecuteQueryAsync()

var definition = new JObject() 
{
    { "id", string.Empty },
    { "title", string.Empty },
    { "first_name", string.Empty },
    { "last_name", string.Empty }
};
var sqlStatement = "SELECT b.id as id, b.title as title, a.first_name as first_name, a.last_name as last_name FROM books b INNER JOIN authors a ON b.author_id = a.id ORDER BY b.id";

var items = await store.ExecuteQueryAsync(definition, sqlStatement, parameters);
// Items is an IList<JObject> where each JObject conforms to the definition.

定義是一組索引鍵/值。 索引鍵必須符合 SQL 查詢傳回的功能變數名稱,而且值必須是預期的類型預設值。 將 0L 用於數位(long)、布爾值 false,以及針對其他所有專案使用 string.Empty

SQLite 有一組嚴格的支持類型。 日期/時間會儲存為自 epoch 以來允許比較的毫秒數。

驗證使用者

Azure Mobile Apps 可讓您產生驗證提供者來處理驗證呼叫。 在建構服務客戶端時指定驗證提供者:

AuthenticationProvider authProvider = GetAuthenticationProvider();
var client = new DatasyncClient("APP_URL", authProvider);

每當需要驗證時,就會呼叫驗證提供者以取得令牌。 泛型驗證提供者可用於授權標頭型驗證和 App Service 驗證和授權型驗證。 使用下列模型:

public AuthenticationProvider GetAuthenticationProvider()
    => new GenericAuthenticationProvider(GetTokenAsync);

// Or, if using Azure App Service Authentication and Authorization
// public AuthenticationProvider GetAuthenticationProvider()
//    => new GenericAuthenticationProvider(GetTokenAsync, "X-ZUMO-AUTH");

public async Task<AuthenticationToken> GetTokenAsync()
{
    // TODO: Any code necessary to get the right access token.
    
    return new AuthenticationToken 
    {
        DisplayName = "/* the display name of the user */",
        ExpiresOn = DateTimeOffset.Now.AddHours(1), /* when does the token expire? */
        Token = "/* the access token */",
        UserId = "/* the user id of the connected user */"
    };
}

驗證令牌會在記憶體中快取(永遠不會寫入裝置),並在必要時重新整理。

使用Microsoft身分識別平臺

Microsoft身分識別平臺可讓您輕鬆地與 Microsoft Entra 識別元整合。 如需如何實作Microsoft Entra 驗證的完整教學課程,請參閱快速入門教學課程。 下列程式代碼顯示擷取存取權杖的範例:

private readonly string[] _scopes = { /* provide your AAD scopes */ };
private readonly object _parentWindow; /* Fill in with the required object before using */
private readonly PublicClientApplication _pca; /* Create one */

public MyAuthenticationHelper(object parentWindow) 
{
    _parentWindow = parentWindow;
    _pca = PublicClientApplicationBuilder.Create(clientId)
            .WithRedirectUri(redirectUri)
            .WithAuthority(authority)
            /* Add options methods here */
            .Build();
}

public async Task<AuthenticationToken> GetTokenAsync()
{
    // Silent authentication
    try
    {
        var account = await _pca.GetAccountsAsync().FirstOrDefault();
        var result = await _pca.AcquireTokenSilent(_scopes, account).ExecuteAsync();
        
        return new AuthenticationToken 
        {
            ExpiresOn = result.ExpiresOn,
            Token = result.AccessToken,
            UserId = result.Account?.Username ?? string.Empty
        };    
    }
    catch (Exception ex) when (exception is not MsalUiRequiredException)
    {
        // Handle authentication failure
        return null;
    }

    // UI-based authentication
    try
    {
        var account = await _pca.AcquireTokenInteractive(_scopes)
            .WithParentActivityOrWindow(_parentWindow)
            .ExecuteAsync();
        
        return new AuthenticationToken 
        {
            ExpiresOn = result.ExpiresOn,
            Token = result.AccessToken,
            UserId = result.Account?.Username ?? string.Empty
        };    
    }
    catch (Exception ex)
    {
        // Handle authentication failure
        return null;
    }
}

如需整合Microsoft身分識別平臺與 ASP.NET 6 的詳細資訊,請參閱 Microsoft 身分識別平臺 檔。

使用 Xamarin Essentials 或 MAUI WebAuthenticator

針對 Azure App Service 驗證,您可以使用 Xamarin Essentials WebAuthenticatorMAUI WebAuthenticator 來取得令牌:

Uri authEndpoint = new Uri(client.Endpoint, "/.auth/login/aad");
Uri callback = new Uri("myapp://easyauth.callback");

public async Task<AuthenticationToken> GetTokenAsync()
{
    var authResult = await WebAuthenticator.AuthenticateAsync(authEndpoint, callback);
    return new AuthenticationToken 
    {
        ExpiresOn = authResult.ExpiresIn,
        Token = authResult.AccessToken
    };
}

使用 Azure App Service 驗證時,無法直接使用 UserIdDisplayName。 請改用延遲要求者,從 /.auth/me 端點擷取資訊:

var userInfo = new AsyncLazy<UserInformation>(() => GetUserInformationAsync());

public async Task<UserInformation> GetUserInformationAsync() 
{
    // Get the token for the current user
    var authInfo = await GetTokenAsync();

    // Construct the request
    var request = new HttpRequestMessage(HttpMethod.Get, new Uri(client.Endpoint, "/.auth/me"));
    request.Headers.Add("X-ZUMO-AUTH", authInfo.Token);

    // Create a new HttpClient, then send the request
    var httpClient = new HttpClient();
    var response = await httpClient.SendAsync(request);

    // If the request is successful, deserialize the content into the UserInformation object.
    // You will have to create the UserInformation class.
    if (response.IsSuccessStatusCode) 
    {
        var content = await response.ReadAsStringAsync();
        return JsonSerializer.Deserialize<UserInformation>(content);
    }
}

進階主題

清除本機資料庫中的實體

在正常作業下,不需要清除實體。 同步處理程式會移除已刪除的實體,並維護本機資料庫數據表的必要元數據。 不過,有時清除資料庫中的實體會很有説明。 其中一種案例是當您需要刪除大量實體,而且從本機抹除數據表的數據更有效率時。

若要清除資料表的記錄,請使用 table.PurgeItemsAsync()

var query = table.CreateQuery();
var purgeOptions = new PurgeOptions();
await table.PurgeItermsAsync(query, purgeOptions, cancellationToken);

查詢會識別要從數據表中移除的實體。 使用 LINQ 識別要清除的實體:

var query = table.CreateQuery().Where(m => m.Archived == true);

PurgeOptions 類別提供修改清除作業的設定:

  • DiscardPendingOperations 捨棄作業佇列中正在等候傳送至伺服器之數據表的任何暫止作業。
  • QueryId 指定查詢識別碼,用來識別要用於作業的差異令牌。
  • TimestampUpdatePolicy 指定如何在清除作業結束時調整差異權杖:
    • TimestampUpdatePolicy.NoUpdate 表示不能更新差異令牌。
    • TimestampUpdatePolicy.UpdateToLastEntity 指出差異令牌應該更新為數據表中最後儲存之實體的 [updatedAt] 字段。
    • TimestampUpdatePolicy.UpdateToNow 表示差異令牌應該更新為目前的日期/時間。
    • TimestampUpdatePolicy.UpdateToEpoch 表示應重設差異令牌以同步處理所有數據。

使用您在呼叫 table.PullItemsAsync() 同步處理資料時所使用的相同 QueryId 值。 QueryId 會指定要在清除完成時更新的差異令牌。

自定義要求標頭

若要支援您的特定應用程式案例,您可能需要自訂與行動應用程式後端的通訊。 例如,您可以將自定義標頭新增至每個傳出要求,或在返回使用者之前變更響應狀態代碼。 使用自定義 委派Handler,如下列範例所示:

public async Task CallClientWithHandler()
{
    var options = new DatasyncClientOptions
    {
        HttpPipeline = new DelegatingHandler[] { new MyHandler() }
    };
    var client = new Datasync("AppUrl", options);
    var todoTable = client.GetRemoteTable<TodoItem>();
    var newItem = new TodoItem { Text = "Hello world", Complete = false };
    await todoTable.InsertItemAsync(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;
    }
}

監視同步處理事件

當同步處理事件發生時,事件會發佈至 client.SynchronizationProgress 事件委派。 事件可用來監視同步處理程序的進度。 定義同步處理事件處理程式,如下所示:

client.SynchronizationProgress += (sender, args) => {
    // args is of type SynchronizationEventArgs
};

SynchronizationEventArgs 型態的定義如下:

public enum SynchronizationEventType
{
    PushStarted,
    ItemWillBePushed,
    ItemWasPushed,
    PushFinished,
    PullStarted,
    ItemWillBeStored,
    ItemWasStored,
    PullFinished
}

public class SynchronizationEventArgs
{
    public SynchronizationEventType EventType { get; }
    public string ItemId { get; }
    public long ItemsProcessed { get; } 
    public long QueueLength { get; }
    public string TableName { get; }
    public bool IsSuccessful { get; }
}

當屬性與同步處理事件無關時,args 內的屬性會 null-1