Cara menggunakan pustaka klien Azure Mobile Apps untuk .NET
Nota
Produk ini dihentikan. Untuk pengganti proyek yang menggunakan .NET 8 atau yang lebih baru, lihat pustaka Community Toolkit Datasync.
Panduan ini menunjukkan kepada Anda cara melakukan skenario umum menggunakan pustaka klien .NET untuk Azure Mobile Apps. Gunakan pustaka klien .NET di aplikasi .NET 6 atau .NET Standard 2.0 apa pun, termasuk MAUI, Xamarin, dan Windows (WPF, UWP, dan WinUI).
Jika Anda baru menggunakan Azure Mobile Apps, pertimbangkan untuk terlebih dahulu menyelesaikan salah satu tutorial mulai cepat:
- AvaloniaUI
- MAUI (Android dan iOS)
- Platform
Uno - Windows (UWP)
- Windows (WinUI3)
-
Windows (WPF) -
Xamarin (Android Native) -
Xamarin (iOS Native) - Xamarin Forms (Android dan iOS)
Nota
Artikel ini membahas edisi terbaru (v6.0) dari Microsoft Datasync Framework. Untuk klien yang lebih lama, lihat dokumentasi v4.2.0.
Platform yang didukung
Pustaka klien .NET mendukung platform .NET Standard 2.0 atau .NET 6 apa pun, termasuk:
- .NET MAUI untuk platform Android, iOS, dan Windows.
- Android API level 21 dan yang lebih baru (Xamarin dan Android untuk .NET).
- iOS versi 12.0 dan yang lebih baru (Xamarin dan iOS untuk .NET).
- Universal Windows Platform membangun 19041 dan yang lebih baru.
- Windows Presentation Framework (WPF).
- Windows App SDK (WinUI 3).
- Xamarin.Forms
Selain itu, sampel telah dibuat untuk Avalonia dan Platform Uno. Sampel TodoApp berisi contoh setiap platform yang diuji.
Penyiapan dan Prasyarat
Tambahkan pustaka berikut dari NuGet:
Jika menggunakan proyek platform (misalnya, .NET MAUI), pastikan Anda menambahkan pustaka ke proyek platform dan proyek bersama apa pun.
Membuat klien layanan
Kode berikut membuat klien layanan, yang digunakan untuk mengoordinasikan semua komunikasi ke tabel backend dan offline.
var options = new DatasyncClientOptions
{
// Options set here
};
var client = new DatasyncClient("MOBILE_APP_URL", options);
Dalam kode sebelumnya, ganti MOBILE_APP_URL
dengan URL backend ASP.NET Core. Klien harus dibuat sebagai singleton. Jika menggunakan penyedia autentikasi, penyedia autentikasi dapat dikonfigurasi seperti ini:
var options = new DatasyncClientOptions
{
// Options set here
};
var client = new DatasyncClient("MOBILE_APP_URL", authProvider, options);
Detail selengkapnya tentang penyedia autentikasi disediakan nanti dalam dokumen ini.
Pilihan
Sekumpulan opsi lengkap (default) dapat dibuat seperti ini:
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
Biasanya, permintaan HTTP dibuat dengan meneruskan permintaan melalui penyedia autentikasi (yang menambahkan header Authorization
untuk pengguna yang saat ini diautentikasi) sebelum mengirim permintaan. Anda dapat, secara opsional, menambahkan lebih banyak penangan pendelegasian. Setiap permintaan melewati penangan pendelegasian sebelum dikirim ke layanan. Mendelegasikan handler memungkinkan Anda menambahkan header tambahan, melakukan percobaan ulang, atau menyediakan kemampuan pengelogan.
Contoh pendelegasian handler disediakan untuk pengelogan
IdGenerator
Saat entitas ditambahkan ke tabel offline, entitas harus memiliki ID. ID dihasilkan jika tidak disediakan. Opsi IdGenerator
memungkinkan Anda menyesuaikan ID yang dihasilkan. Secara default, ID unik global dihasilkan. Misalnya, pengaturan berikut menghasilkan string yang menyertakan nama tabel dan GUID:
var options = new DatasyncClientOptions
{
IdGenerator = (table) => $"{table}-{Guid.NewGuid().ToString("D").ToUpperInvariant()}"
}
InstallationId
Jika InstallationId
diatur, header kustom X-ZUMO-INSTALLATION-ID
dikirim dengan setiap permintaan untuk mengidentifikasi kombinasi aplikasi pada perangkat tertentu. Header ini dapat direkam dalam log dan memungkinkan Anda menentukan jumlah penginstalan yang berbeda untuk aplikasi Anda. Jika Anda menggunakan InstallationId
, ID harus disimpan dalam penyimpanan persisten pada perangkat sehingga penginstalan unik dapat dilacak.
OfflineStore
OfflineStore
digunakan saat mengonfigurasi akses data offline. Untuk informasi selengkapnya, lihat Bekerja dengan tabel offline.
ParallelOperations
Bagian dari proses sinkronisasi offline melibatkan pendorongan operasi antrean ke server jarak jauh. Ketika operasi pendorongan dipicu, operasi dikirimkan dalam urutan yang diterima. Anda dapat, secara opsional, menggunakan hingga delapan utas untuk mendorong operasi ini. Operasi paralel menggunakan lebih banyak sumber daya pada klien dan server untuk menyelesaikan operasi lebih cepat. Urutan saat operasi tiba di server tidak dapat dijamin saat menggunakan beberapa utas.
SerializerSettings
Jika Anda telah mengubah pengaturan serializer di server sinkronisasi data, Anda perlu membuat perubahan yang sama pada SerializerSettings
pada klien. Opsi ini memungkinkan Anda menentukan pengaturan serializer Anda sendiri.
TableEndpointResolver
Menurut konvensi, tabel terletak di layanan jarak jauh di jalur /tables/{tableName}
(seperti yang ditentukan oleh atribut Route
dalam kode server). Namun, tabel dapat ada di jalur titik akhir mana pun.
TableEndpointResolver
adalah fungsi yang mengubah nama tabel menjadi jalur untuk berkomunikasi dengan layanan jarak jauh.
Misalnya, berikut mengubah asumsi sehingga semua tabel terletak di bawah /api
:
var options = new DatasyncClientOptions
{
TableEndpointResolver = (table) => $"/api/{table}"
};
UserAgent
Klien sinkronisasi data menghasilkan nilai header User-Agent yang sesuai berdasarkan versi pustaka. Beberapa pengembang merasa header agen pengguna membocorkan informasi tentang klien. Anda dapat mengatur properti UserAgent
ke nilai header yang valid.
Bekerja dengan tabel jarak jauh
Bagian berikut ini merinci cara mencari dan mengambil rekaman dan mengubah data dalam tabel jarak jauh. Topik berikut dibahas:
- Membuat referensi tabel
- data Kueri
- Menghitung item dari kueri
- Mencari data jarak jauh berdasarkan ID
- Menyisipkan data di server jarak jauh
- Memperbarui data di server jarak jauh
- Menghapus data di server jarak jauh
- Resolusi konflik dan konkurensi optimis
Membuat referensi tabel jarak jauh
Untuk membuat referensi tabel jarak jauh, gunakan GetRemoteTable<T>
:
IRemoteTable<TodoItem> remoteTable = client.GetRemoteTable();
Jika Anda ingin mengembalikan tabel baca-saja, gunakan versi IReadOnlyRemoteTable<T>
:
IReadOnlyRemoteTable<TodoItem> remoteTable = client.GetRemoteTable();
Jenis model harus menerapkan kontrak ITableData
dari layanan. Gunakan DatasyncClientData
untuk menyediakan bidang yang diperlukan:
public class TodoItem : DatasyncClientData
{
public string Title { get; set; }
public bool IsComplete { get; set; }
}
Objek DatasyncClientData
meliputi:
-
Id
(string) - ID unik global untuk item. -
UpdatedAt
(System.DataTimeOffset) - tanggal/waktu item terakhir diperbarui. -
Version
(string) - string buram yang digunakan untuk penerapan versi. -
Deleted
(boolean) - jikatrue
, item akan dihapus.
Layanan ini mempertahankan bidang-bidang ini. Jangan sesuaikan bidang ini sebagai bagian dari aplikasi klien Anda.
Model dapat diannotasi menggunakan atribut Newtonsoft.JSON. Nama tabel dapat ditentukan dengan menggunakan atribut DataTable
:
[DataTable("todoitem")]
public class MyTodoItemClass : DatasyncClientData
{
public string Title { get; set; }
public bool IsComplete { get; set; }
}
Atau, tentukan nama tabel dalam panggilan GetRemoteTable()
:
IRemoteTable<TodoItem> remoteTable = client.GetRemoteTable("todoitem");
Klien menggunakan jalur /tables/{tablename}
sebagai URI. Nama tabel juga merupakan nama tabel offline dalam database SQLite.
Jenis yang didukung
Selain jenis primitif (int, float, string, dll.), jenis berikut ini didukung untuk model:
-
System.DateTime
- sebagai string tanggal/waktu UTC ISO-8601 dengan akurasi ms. -
System.DateTimeOffset
- sebagai string tanggal/waktu UTC ISO-8601 dengan akurasi ms. -
System.Guid
- diformat sebagai 32 digit dipisahkan sebagai tanda hubung.
Mengkueri data dari server jarak jauh
Tabel jarak jauh dapat digunakan dengan pernyataan seperti LINQ, termasuk:
- Pemfilteran dengan klausa
.Where()
. - Mengurutkan dengan berbagai klausa
.OrderBy()
. - Memilih properti dengan
.Select()
. - Halaman dengan
.Skip()
dan.Take()
.
Menghitung item dari kueri
Jika Anda memerlukan hitungan item yang akan dikembalikan kueri, Anda bisa menggunakan .CountItemsAsync()
pada tabel atau .LongCountAsync()
pada kueri:
// 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();
Metode ini menyebabkan perjalanan pulang pergi ke server. Anda juga bisa mendapatkan hitungan saat mengisi daftar (misalnya), menghindari pulang-pergi tambahan:
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);
}
Jumlah akan diisi setelah permintaan pertama untuk mengambil konten tabel.
Mengembalikan semua data
Data dikembalikan melalui
var enumerable = remoteTable.ToAsyncEnumerable();
await foreach (var item in enumerable)
{
// Process each item
}
Gunakan salah satu klausul penghentian berikut untuk mengonversi IAsyncEnumerable<T>
ke koleksi yang berbeda:
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();
Di balik layar, tabel jarak jauh menangani halaman hasil untuk Anda. Semua item dikembalikan terlepas dari berapa banyak permintaan sisi server yang diperlukan untuk memenuhi kueri. Elemen-elemen ini juga tersedia pada hasil kueri (misalnya, remoteTable.Where(m => m.Rating == "R")
).
Kerangka kerja Sinkronisasi data juga menyediakan ConcurrentObservableCollection<T>
- koleksi yang dapat diamati dengan aman utas. Kelas ini dapat digunakan dalam konteks aplikasi UI yang biasanya menggunakan ObservableCollection<T>
untuk mengelola daftar (misalnya, formulir Xamarin atau daftar MAUI). Anda dapat menghapus dan memuat ConcurrentObservableCollection<T>
langsung dari tabel atau kueri:
var collection = new ConcurrentObservableCollection<T>();
await remoteTable.ToObservableCollection(collection);
Menggunakan .ToObservableCollection(collection)
memicu peristiwa CollectionChanged
sekali untuk seluruh koleksi daripada untuk item individual, menghasilkan waktu penguraian ulang yang lebih cepat.
ConcurrentObservableCollection<T>
juga memiliki modifikasi berbasis predikat:
// 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);
Modifikasi berbasis predikat dapat digunakan dalam penanganan aktivitas saat indeks item tidak diketahui sebelumnya.
Memfilter data
Anda dapat menggunakan klausa .Where()
untuk memfilter data. Misalnya:
var items = await remoteTable.Where(x => !x.IsComplete).ToListAsync();
Pemfilteran dilakukan pada layanan sebelum IAsyncEnumerable dan pada klien setelah IAsyncEnumerable. Misalnya:
var items = (await remoteTable.Where(x => !x.IsComplete).ToListAsync()).Where(x => x.Title.StartsWith("The"));
Klausa .Where()
pertama (hanya mengembalikan item yang tidak lengkap) dijalankan pada layanan, sedangkan klausa .Where()
kedua (dimulai dengan "The") dijalankan pada klien.
Klausa Where
mendukung operasi yang diterjemahkan ke dalam subset OData. Operasi meliputi:
- Operator relasional (
==
,!=
,<
,<=
,>
,>=
), - Operator aritmatika (
+
,-
,/
,*
,%
), - Presisi angka (
Math.Floor
,Math.Ceiling
), - Fungsi string (
Length
,Substring
,Replace
,IndexOf
,Equals
,StartsWith
,EndsWith
) (hanya budaya ordinal dan invarian), - Properti tanggal (
Year
,Month
,Day
,Hour
,Minute
,Second
), - Mengakses properti objek, dan
- Ekspresi yang menggabungkan salah satu operasi ini.
Mengurutkan data
Gunakan .OrderBy()
, .OrderByDescending()
, .ThenBy()
, dan .ThenByDescending()
dengan aksesor properti untuk mengurutkan data.
var items = await remoteTable.OrderBy(x => x.IsComplete).ThenBy(x => x.Title).ToListAsync();
Penyortiran dilakukan oleh layanan. Anda tidak dapat menentukan ekspresi dalam klausa pengurutan apa pun. Jika Anda ingin mengurutkan menurut ekspresi, gunakan pengurutan sisi klien:
var items = await remoteTable.ToListAsync().OrderBy(x => x.Title.ToLowerCase());
Memilih properti
Anda dapat mengembalikan subkumpulan data dari layanan:
var items = await remoteTable.Select(x => new { x.Id, x.Title, x.IsComplete }).ToListAsync();
Mengembalikan halaman data
Anda dapat mengembalikan subset himpunan data menggunakan .Skip()
dan .Take()
untuk mengimplementasikan penomor:
var pageOfItems = await remoteTable.Skip(100).Take(10).ToListAsync();
Di aplikasi dunia nyata, Anda dapat menggunakan kueri yang mirip dengan contoh sebelumnya dengan kontrol pager atau antarmuka pengguna yang sebanding untuk menavigasi antar halaman.
Semua fungsi yang dijelaskan sejauh ini bersifat aditif, sehingga kita dapat terus menautkannya. Setiap panggilan berantai memengaruhi lebih banyak kueri. Satu contoh lagi:
var query = todoTable
.Where(todoItem => todoItem.Complete == false)
.Select(todoItem => todoItem.Text)
.Skip(3).
.Take(3);
List<string> items = await query.ToListAsync();
Mencari data jarak jauh berdasarkan ID
Fungsi GetItemAsync
dapat digunakan untuk mencari objek dari database dengan ID tertentu.
TodoItem item = await remoteTable.GetItemAsync("37BBF396-11F0-4B39-85C8-B319C729AF6D");
Jika item yang Anda coba ambil telah dihapus sementara, Anda harus menggunakan parameter 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);
Menyisipkan data di server jarak jauh
Semua jenis klien harus berisi anggota bernama Id, yang secara default merupakan string. Id ini diperlukan untuk melakukan operasi CRUD dan untuk sinkronisasi offline. Kode berikut mengilustrasikan cara menggunakan metode InsertItemAsync
untuk menyisipkan baris baru ke dalam tabel. Parameter berisi data yang akan dimasukkan sebagai objek .NET.
var item = new TodoItem { Title = "Text", IsComplete = false };
await remoteTable.InsertItemAsync(item);
// Note that item.Id will now be set
Jika nilai ID kustom unik tidak disertakan dalam item
selama penyisipan, server akan menghasilkan ID. Anda dapat mengambil ID yang dihasilkan dengan memeriksa objek setelah panggilan kembali.
Memperbarui data di server jarak jauh
Kode berikut ini menggambarkan cara menggunakan metode ReplaceItemAsync
untuk memperbarui rekaman yang ada dengan ID yang sama dengan informasi baru.
// In this example, we assume the item has been created from the InsertItemAsync sample
item.IsComplete = true;
await remoteTable.ReplaceItemAsync(todoItem);
Menghapus data di server jarak jauh
Kode berikut mengilustrasikan cara menggunakan metode DeleteItemAsync
untuk menghapus instans yang ada.
// In this example, we assume the item has been created from the InsertItemAsync sample
await todoTable.DeleteItemAsync(item);
Resolusi konflik dan konkurensi optimis
Dua klien atau lebih dapat menulis perubahan pada item yang sama secara bersamaan. Tanpa deteksi konflik, tulisan terakhir akan menimpa pembaruan sebelumnya. Kontrol konkurensi optimis mengasumsikan bahwa setiap transaksi dapat diterapkan dan oleh karena itu tidak menggunakan penguncian sumber daya apa pun. Kontrol konkurensi optimis memverifikasi bahwa tidak ada transaksi lain yang telah memodifikasi data sebelum melakukan data. Jika data telah dimodifikasi, transaksi akan digulung balik.
Azure Mobile Apps mendukung kontrol konkurensi optimis dengan melacak perubahan pada setiap item menggunakan kolom properti sistem version
yang ditentukan untuk setiap tabel di backend Mobile App Anda. Setiap kali rekaman diperbarui, Mobile Apps mengatur properti version
untuk rekaman tersebut ke nilai baru. Selama setiap permintaan pembaruan, properti version
catatan yang disertakan dengan permintaan dibandingkan dengan properti yang sama untuk rekaman di server. Jika versi yang diteruskan dengan permintaan tidak cocok dengan backend, maka pustaka klien akan memunculkan pengecualian DatasyncConflictException<T>
. Jenis yang disertakan dengan pengecualian adalah rekaman dari backend yang berisi versi server rekaman. Aplikasi kemudian dapat menggunakan informasi ini untuk memutuskan apakah akan menjalankan permintaan pembaruan lagi dengan nilai version
yang benar dari backend untuk menerapkan perubahan.
Konkurensi optimis diaktifkan secara otomatis saat menggunakan objek dasar DatasyncClientData
.
Selain mengaktifkan konkurensi optimis, Anda juga harus menangkap pengecualian DatasyncConflictException<T>
dalam kode Anda. Atasi konflik dengan menerapkan version
yang benar ke rekaman yang diperbarui lalu ulangi panggilan dengan rekaman yang diselesaikan. Kode berikut menunjukkan cara mengatasi konflik tulis setelah terdeteksi:
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();
}
Bekerja dengan tabel offline
Tabel offline menggunakan penyimpanan SQLite lokal untuk menyimpan data untuk digunakan saat offline. Semua operasi tabel dilakukan terhadap penyimpanan SQLite lokal alih-alih penyimpanan server jarak jauh. Pastikan Anda menambahkan Microsoft.Datasync.Client.SQLiteStore
ke setiap proyek platform dan ke proyek bersama apa pun.
Sebelum referensi tabel dapat dibuat, penyimpanan lokal harus disiapkan:
var store = new OfflineSQLiteStore(Constants.OfflineConnectionString);
store.DefineTable<TodoItem>();
Setelah penyimpanan ditentukan, Anda dapat membuat klien:
var options = new DatasyncClientOptions
{
OfflineStore = store
};
var client = new DatasyncClient("MOBILE_URL", options);
Terakhir, Anda harus memastikan bahwa kemampuan offline diinisialisasi:
await client.InitializeOfflineStoreAsync();
Inisialisasi penyimpanan biasanya dilakukan segera setelah klien dibuat.
- Untuk menggunakan cache dalam memori, gunakan
file:inmemory.db?mode=memory&cache=private
. - Untuk menggunakan file, gunakan
file:/path/to/file.db
Anda harus menentukan nama file absolut untuk file tersebut. Jika menggunakan Xamarin, Anda dapat menggunakan Xamarin Essentials File System Helpers untuk membangun jalur: Misalnya:
var dbPath = $"{Filesystem.AppDataDirectory}/todoitems.db";
var store = new OfflineSQLiteStore($"file:/{dbPath}?mode=rwc");
Jika Anda menggunakan MAUI, Anda dapat menggunakan
var dbPath = $"{Filesystem.AppDataDirectory}/todoitems.db";
var store = new OfflineSQLiteStore($"file:/{dbPath}?mode=rwc");
Membuat tabel offline
Referensi tabel dapat diperoleh menggunakan metode GetOfflineTable<T>
:
IOfflineTable<TodoItem> table = client.GetOfflineTable<TodoItem>();
Seperti halnya tabel jarak jauh, Anda juga dapat mengekspos tabel offline baca-saja:
IReadOnlyOfflineTable<TodoItem> table = client.GetOfflineTable<TodoItem>();
Anda tidak perlu mengautentikasi untuk menggunakan tabel offline. Anda hanya perlu mengautentikasi saat berkomunikasi dengan layanan backend.
Menyinkronkan Tabel Offline
Tabel offline tidak disinkronkan dengan backend secara default. Sinkronisasi dibagi menjadi dua bagian. Anda dapat mendorong perubahan secara terpisah dari mengunduh item baru. Misalnya:
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"]);
}
}
}
Secara default, semua tabel menggunakan sinkronisasi bertambah bertahap - hanya rekaman baru yang diambil. Catatan disertakan untuk setiap kueri unik (dihasilkan dengan membuat hash MD5 dari kueri OData).
Nota
Argumen pertama untuk PullItemsAsync
adalah kueri OData yang menunjukkan rekaman mana yang akan ditarik ke perangkat. Lebih baik memodifikasi layanan untuk hanya mengembalikan rekaman khusus untuk pengguna daripada membuat kueri kompleks di sisi klien.
Opsi (ditentukan oleh objek PullOptions
) umumnya tidak perlu diatur. Opsinya meliputi:
-
PushOtherTables
- jika diatur ke true, semua tabel akan didorong. -
QueryId
- ID kueri tertentu untuk digunakan daripada yang dihasilkan. -
WriteDeltaTokenInterval
- seberapa sering menulis delta-token yang digunakan untuk melacak sinkronisasi inkremental.
SDK melakukan PushAsync()
implisit sebelum menarik rekaman.
Penanganan konflik terjadi pada metode PullAsync()
. Tangani konflik dengan cara yang sama seperti tabel online. Konflik dihasilkan ketika PullAsync()
dipanggil alih-alih selama penyisipan, pembaruan, atau penghapusan. Jika beberapa konflik terjadi, konflik tersebut dibundel ke dalam satu PushFailedException
. Tangani setiap kegagalan secara terpisah.
Mendorong perubahan untuk semua tabel
Untuk mendorong semua perubahan ke server jarak jauh, gunakan:
await client.PushTablesAsync();
Untuk mendorong perubahan subkumpulan tabel, berikan IEnumerable<string>
ke metode PushTablesAsync()
:
var tablesToPush = new string[] { "TodoItem", "Notes" };
await client.PushTables(tablesToPush);
Gunakan properti client.PendingOperations
untuk membaca jumlah operasi yang menunggu untuk didorong ke layanan jarak jauh. Properti ini null
ketika tidak ada penyimpanan offline yang dikonfigurasi.
Menjalankan kueri SQLite yang kompleks
Jika Anda perlu melakukan kueri SQL kompleks terhadap database offline, Anda dapat melakukannya menggunakan metode ExecuteQueryAsync()
. Misalnya, untuk melakukan pernyataan SQL JOIN
, tentukan JObject
yang menunjukkan struktur nilai pengembalian, lalu gunakan 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.
Definisinya adalah sekumpulan kunci/nilai. Kunci harus cocok dengan nama bidang yang dikembalikan kueri SQL, dan nilainya harus merupakan nilai default dari jenis yang diharapkan. Gunakan 0L
untuk angka (panjang), false
untuk boolean, dan string.Empty
untuk yang lainnya.
SQLite memiliki sekumpulan jenis yang didukung secara ketat. Tanggal/waktu disimpan sebagai jumlah milidetik karena zaman untuk memungkinkan perbandingan.
Mengautentikasi pengguna
Azure Mobile Apps memungkinkan Anda membuat penyedia autentikasi untuk menangani panggilan autentikasi. Tentukan penyedia autentikasi saat membuat klien layanan:
AuthenticationProvider authProvider = GetAuthenticationProvider();
var client = new DatasyncClient("APP_URL", authProvider);
Setiap kali autentikasi diperlukan, penyedia autentikasi dipanggil untuk mendapatkan token. Penyedia autentikasi generik dapat digunakan untuk autentikasi berbasis header Otorisasi dan Autentikasi App Service dan autentikasi berbasis Otorisasi. Gunakan model berikut:
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 */"
};
}
Token autentikasi di-cache dalam memori (tidak pernah ditulis ke perangkat) dan disegarkan jika perlu.
Menggunakan platform identitas Microsoft
Platform identitas Microsoft memungkinkan Anda untuk dengan mudah berintegrasi dengan MICROSOFT Entra ID. Lihat tutorial mulai cepat untuk tutorial lengkap tentang cara menerapkan autentikasi Microsoft Entra. Kode berikut menunjukkan contoh pengambilan token akses:
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;
}
}
Untuk informasi selengkapnya tentang mengintegrasikan platform identitas Microsoft dengan ASP.NET 6, lihat dokumentasi platform identitas Microsoft
Menggunakan Xamarin Essentials atau MAUI WebAuthenticator
Untuk Autentikasi Azure App Service, Anda dapat menggunakan
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
};
}
UserId
dan DisplayName
tidak tersedia secara langsung saat menggunakan Autentikasi Azure App Service. Sebagai gantinya, gunakan pemohon malas untuk mengambil informasi dari titik akhir /.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);
}
}
Topik tingkat lanjut
Membersihkan entitas dalam database lokal
Dalam operasi normal, entitas pembersihan tidak diperlukan. Proses sinkronisasi menghapus entitas yang dihapus dan mempertahankan metadata yang diperlukan untuk tabel database lokal. Namun, ada kalanya membersihkan entitas dalam database sangat membantu. Salah satu skenario tersebut adalah ketika Anda perlu menghapus sejumlah besar entitas dan lebih efisien untuk menghapus data dari tabel secara lokal.
Untuk menghapus menyeluruh rekaman dari tabel, gunakan table.PurgeItemsAsync()
:
var query = table.CreateQuery();
var purgeOptions = new PurgeOptions();
await table.PurgeItermsAsync(query, purgeOptions, cancellationToken);
Kueri mengidentifikasi entitas yang akan dihapus dari tabel. Identifikasi entitas yang akan dibersihkan menggunakan LINQ:
var query = table.CreateQuery().Where(m => m.Archived == true);
Kelas PurgeOptions
menyediakan pengaturan untuk mengubah operasi penghapusan menyeluruh:
-
DiscardPendingOperations
membuang operasi yang tertunda untuk tabel yang berada dalam antrean operasi yang menunggu untuk dikirim ke server. -
QueryId
menentukan ID kueri yang digunakan untuk mengidentifikasi token delta yang akan digunakan untuk operasi. -
TimestampUpdatePolicy
menentukan cara menyesuaikan token delta di akhir operasi pembersihan:-
TimestampUpdatePolicy.NoUpdate
menunjukkan token delta tidak boleh diperbarui. -
TimestampUpdatePolicy.UpdateToLastEntity
menunjukkan token delta harus diperbarui ke bidangupdatedAt
untuk entitas terakhir yang disimpan dalam tabel. -
TimestampUpdatePolicy.UpdateToNow
menunjukkan token delta harus diperbarui ke tanggal/waktu saat ini. -
TimestampUpdatePolicy.UpdateToEpoch
menunjukkan token delta harus diatur ulang untuk menyinkronkan semua data.
-
Gunakan nilai QueryId
yang sama dengan yang Anda gunakan saat memanggil table.PullItemsAsync()
untuk menyinkronkan data.
QueryId
menentukan token delta untuk diperbarui saat pembersihan selesai.
Mengkustomisasi header permintaan
Untuk mendukung skenario aplikasi tertentu, Anda mungkin perlu menyesuaikan komunikasi dengan backend Aplikasi Seluler. Misalnya, Anda dapat menambahkan header kustom ke setiap permintaan keluar atau mengubah kode status respons sebelum kembali ke pengguna. Gunakan kustom DelegatingHandler, seperti dalam contoh berikut:
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;
}
}
Aktifkan pengelogan permintaan
Anda juga dapat menggunakan DelegatingHandler untuk menambahkan pengelogan permintaan:
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;
}
}
Memantau peristiwa sinkronisasi
Saat peristiwa sinkronisasi terjadi, peristiwa diterbitkan ke delegasi peristiwa client.SynchronizationProgress
. Peristiwa dapat digunakan untuk memantau kemajuan proses sinkronisasi. Tentukan penanganan aktivitas sinkronisasi sebagai berikut:
client.SynchronizationProgress += (sender, args) => {
// args is of type SynchronizationEventArgs
};
Jenis SynchronizationEventArgs
didefinisikan sebagai berikut:
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; }
}
Properti dalam args
null
atau -1
saat properti tidak relevan dengan peristiwa sinkronisasi.