如何使用適用於 .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,請考慮先完成其中一個快速入門教學課程:
- 阿瓦洛尼亞UI
- MAUI (Android 和 iOS)
- Uno Platform
- Windows (UWP)
- Windows (WinUI3)
- Windows (WPF)
- Xamarin (Android Native)
- Xamarin (iOS Native)
- Xamarin Forms (Android 和 iOS)
注意
本文涵蓋 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
設定和必要條件
從 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
,則會刪除專案。
服務會維護這些欄位。 請勿將這些欄位調整為用戶端應用程式的一部分。
您可以使用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.Floor
,Math.Ceiling
), - 字串函式(
Length
、Substring
、Replace
、IndexOf
、Equals
、StartsWith
、EndsWith
)(僅限序數和不變的文化特性), - 日期屬性(
Year
、Month
、Day
、Hour
、Minute
、Second
)、 - 存取物件的屬性,以及
- 結合上述任何作業的表達式。
排序數據
搭配屬性存取子使用 .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。 如需詳細資訊,請參閱 SQLite
- 若要使用記憶體內部快取,請使用
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 WebAuthenticator 或 MAUI 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 驗證時,無法直接使用 UserId
和 DisplayName
。 請改用延遲要求者,從 /.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
。