Verwenden der Azure Mobile Apps-Clientbibliothek für .NET
Anmerkung
Dieses Produkt wird eingestellt. Eine Ersetzung für Projekte mit .NET 8 oder höher finden Sie in der Community Toolkit Datasync-Bibliothek.
In diesem Handbuch erfahren Sie, wie Sie allgemeine Szenarien mit der .NET-Clientbibliothek für Azure Mobile Apps ausführen. Verwenden Sie die .NET-Clientbibliothek in einer beliebigen .NET 6- oder .NET Standard 2.0-Anwendung, einschließlich MAUI, Xamarin und Windows (WPF, UWP und WinUI).
Wenn Sie mit Azure Mobile Apps noch nicht vertraut sind, sollten Sie zunächst eines der Schnellstartlernprogramme abschließen:
- AvaloniaUI
- MAUI (Android und iOS)
- Uno Platform
- Windows(UWP)-
- Windows (WinUI3)
- Windows(WPF)-
- Xamarin (Android Native)
- Xamarin (iOS Native)
- Xamarin Forms (Android und iOS)
Anmerkung
In diesem Artikel wird die neueste Version (v6.0) des Microsoft Datasync Framework behandelt. Informationen zu älteren Clients finden Sie in der v4.2.0-Dokumentation.
Unterstützte Plattformen
Die .NET-Clientbibliothek unterstützt jede .NET Standard 2.0- oder .NET 6-Plattform, einschließlich:
- .NET MAUI für Android-, iOS- und Windows-Plattformen.
- Android-API-Ebene 21 und höher (Xamarin und Android für .NET).
- iOS Version 12.0 und höher (Xamarin und iOS für .NET).
- Universelle Windows-Plattform builds 19041 und höher.
- Windows Presentation Framework (WPF).
- Windows App SDK (WinUI 3).
- Xamarin.Forms
Darüber hinaus wurden Proben für Avalonia und Uno Platformerstellt. Das TodoApp-Beispiel enthält ein Beispiel für jede getestete Plattform.
Setup und Voraussetzungen
Fügen Sie die folgenden Bibliotheken aus NuGet hinzu:
- Microsoft.Datasync.Client-
- Microsoft.Datasync.Client.SQLiteStore, wenn Offlinetabellen verwendet werden.
Wenn Sie ein Plattformprojekt (z. B. .NET MAUI) verwenden, stellen Sie sicher, dass Sie die Bibliotheken zum Plattformprojekt und allen freigegebenen Projekten hinzufügen.
Erstellen des Dienstclients
Der folgende Code erstellt den Dienstclient, der verwendet wird, um die gesamte Kommunikation mit den Back-End- und Offlinetabellen zu koordinieren.
var options = new DatasyncClientOptions
{
// Options set here
};
var client = new DatasyncClient("MOBILE_APP_URL", options);
Ersetzen Sie im vorherigen Code MOBILE_APP_URL
durch die URL des ASP.NET Core-Back-End-. Der Client sollte als Singleton erstellt werden. Wenn Sie einen Authentifizierungsanbieter verwenden, kann er wie folgt konfiguriert werden:
var options = new DatasyncClientOptions
{
// Options set here
};
var client = new DatasyncClient("MOBILE_APP_URL", authProvider, options);
Weitere Details zum Authentifizierungsanbieter werden weiter unten in diesem Dokument bereitgestellt.
Optionen
Eine vollständige (Standardeinstellung) von Optionen kann wie folgt erstellt werden:
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
Normalerweise erfolgt eine HTTP-Anforderung durch Übergeben der Anforderung über den Authentifizierungsanbieter (der den Authorization
Header für den aktuell authentifizierten Benutzer hinzufügt), bevor die Anforderung gesendet wird. Sie können optional weitere Delegierungshandler hinzufügen. Jede Anforderung durchläuft die delegierenden Handler, bevor sie an den Dienst gesendet werden. Durch Delegieren von Handlern können Sie zusätzliche Header hinzufügen, Wiederholungen durchführen oder Protokollierungsfunktionen bereitstellen.
Beispiele für delegierende Handler werden für Protokollierung und Hinzufügen von Anforderungsheadern weiter unten in diesem Artikel bereitgestellt.
IdGenerator
Wenn einer Offlinetabelle eine Entität hinzugefügt wird, muss sie über eine ID verfügen. Wenn keine ID angegeben wird, wird eine ID generiert. Mit der Option IdGenerator
können Sie die generierte ID anpassen. Standardmäßig wird eine global eindeutige ID generiert. Die folgende Einstellung generiert beispielsweise eine Zeichenfolge, die den Tabellennamen und eine GUID enthält:
var options = new DatasyncClientOptions
{
IdGenerator = (table) => $"{table}-{Guid.NewGuid().ToString("D").ToUpperInvariant()}"
}
InstallationId
Wenn ein InstallationId
festgelegt ist, wird eine benutzerdefinierte Kopfzeile X-ZUMO-INSTALLATION-ID
mit jeder Anforderung gesendet, um die Kombination der Anwendung auf einem bestimmten Gerät zu identifizieren. Dieser Header kann in Protokollen aufgezeichnet werden und ermöglicht es Ihnen, die Anzahl der unterschiedlichen Installationen für Ihre App zu ermitteln. Wenn Sie InstallationId
verwenden, sollte die ID im beständigen Speicher auf dem Gerät gespeichert werden, damit eindeutige Installationen nachverfolgt werden können.
OfflineStore
Die OfflineStore
wird beim Konfigurieren des Offlinedatenzugriffs verwendet. Weitere Informationen finden Sie unter Arbeiten mit Offlinetabellen.
ParallelOperations
Ein Teil des Offlinesynchronisierungsprozesses umfasst Pushvorgänge in der Warteschlange an den Remoteserver. Wenn der Pushvorgang ausgelöst wird, werden die Vorgänge in der Reihenfolge übermittelt, in der sie empfangen wurden. Sie können optional bis zu acht Threads verwenden, um diese Vorgänge zu übertragen. Parallele Vorgänge verwenden mehr Ressourcen sowohl auf Dem Client als auch auf dem Server, um den Vorgang schneller abzuschließen. Die Reihenfolge, in der Vorgänge auf dem Server ankommen, kann nicht garantiert werden, wenn mehrere Threads verwendet werden.
SerializerSettings
Wenn Sie die Serialisierungseinstellungen auf dem Datensynchronisierungsserver geändert haben, müssen Sie dieselben Änderungen an der SerializerSettings
auf dem Client vornehmen. Mit dieser Option können Sie Ihre eigenen Serialisierungseinstellungen angeben.
TableEndpointResolver
In der Konvention befinden sich Tabellen im Remotedienst im /tables/{tableName}
Pfad (wie durch das attribut Route
im Servercode angegeben). Tabellen können jedoch auf jedem Endpunktpfad vorhanden sein. Die TableEndpointResolver
ist eine Funktion, die einen Tabellennamen in einen Pfad für die Kommunikation mit dem Remotedienst verwandelt.
Die folgende Änderung ändert beispielsweise die Annahme, dass sich alle Tabellen unter /api
befinden:
var options = new DatasyncClientOptions
{
TableEndpointResolver = (table) => $"/api/{table}"
};
UserAgent
Der Datensynchronisierungsclient generiert basierend auf der Version der Bibliothek einen geeigneten User-Agent Headerwert. Einige Entwickler haben das Gefühl, dass der Benutzer-Agent-Header Informationen über den Client verleckt. Sie können die eigenschaft UserAgent
auf einen beliebigen gültigen Headerwert festlegen.
Arbeiten mit Remotetabellen
Im folgenden Abschnitt wird erläutert, wie Datensätze gesucht und abgerufen und die Daten in einer Remotetabelle geändert werden. Die folgenden Themen werden behandelt:
- Erstellen eines Tabellenverweises
- Abfragedaten
- Zählen von Elementen aus einer Abfrage
- Nachschlagen von Remotedaten nach ID-
- Einfügen von Daten auf dem Remoteserver
- Aktualisieren von Daten auf dem Remoteserver
- Löschen von Daten auf dem Remoteserver
- Konfliktlösung und optimistische Parallelität
Erstellen eines Remotetabellenverweises
Verwenden Sie GetRemoteTable<T>
, um einen Remotetabellenverweis zu erstellen:
IRemoteTable<TodoItem> remoteTable = client.GetRemoteTable();
Wenn Sie eine schreibgeschützte Tabelle zurückgeben möchten, verwenden Sie die IReadOnlyRemoteTable<T>
Version:
IReadOnlyRemoteTable<TodoItem> remoteTable = client.GetRemoteTable();
Der Modelltyp muss den ITableData
Vertrag aus dem Dienst implementieren. Verwenden Sie DatasyncClientData
, um die erforderlichen Felder bereitzustellen:
public class TodoItem : DatasyncClientData
{
public string Title { get; set; }
public bool IsComplete { get; set; }
}
Das DatasyncClientData
-Objekt umfasst:
-
Id
(Zeichenfolge) – eine global eindeutige ID für das Element. -
UpdatedAt
(System.DataTimeOffset) – Das Datum/die Uhrzeit, zu der das Element zuletzt aktualisiert wurde. -
Version
(Zeichenfolge) – eine undurchsichtige Zeichenfolge, die für die Versionsverwaltung verwendet wird. -
Deleted
(boolescher Wert) – wenntrue
, wird das Element gelöscht.
Der Dienst verwaltet diese Felder. Passen Sie diese Felder nicht als Teil Der Clientanwendung an.
Modelle können mit Newtonsoft.JSON Attributenkommentiert werden. Der Name der Tabelle kann mithilfe des attributs DataTable
angegeben werden:
[DataTable("todoitem")]
public class MyTodoItemClass : DatasyncClientData
{
public string Title { get; set; }
public bool IsComplete { get; set; }
}
Geben Sie alternativ den Namen der Tabelle im GetRemoteTable()
Aufruf an:
IRemoteTable<TodoItem> remoteTable = client.GetRemoteTable("todoitem");
Der Client verwendet den Pfad /tables/{tablename}
als URI. Der Tabellenname ist auch der Name der Offlinetabelle in der SQLite-Datenbank.
Unterstützte Typen
Neben primitiven Typen (int, float, string usw.) werden die folgenden Typen für Modelle unterstützt:
-
System.DateTime
– als ISO-8601 UTC-Datums-/Uhrzeitzeichenfolge mit ms-Genauigkeit. -
System.DateTimeOffset
– als ISO-8601 UTC-Datums-/Uhrzeitzeichenfolge mit ms-Genauigkeit. -
System.Guid
- als 32 Ziffern formatiert als Bindestriche getrennt.
Abfragen von Daten von einem Remoteserver
Die Remotetabelle kann mit LINQ-ähnlichen Anweisungen verwendet werden, einschließlich:
- Filtern mit einer
.Where()
-Klausel. - Sortieren mit verschiedenen
.OrderBy()
Klauseln. - Auswählen von Eigenschaften mit
.Select()
. - Paging mit
.Skip()
und.Take()
.
Zählen von Elementen aus einer Abfrage
Wenn Sie eine Anzahl der Elemente benötigen, die die Abfrage zurückgeben würde, können Sie .CountItemsAsync()
für eine Tabelle oder .LongCountAsync()
für eine Abfrage verwenden:
// 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();
Diese Methode verursacht einen Roundtrip zum Server. Sie können auch beim Auffüllen einer Liste (z. B. eine Zählung) abrufen, um den zusätzlichen Roundtrip zu vermeiden:
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);
}
Die Anzahl wird nach der ersten Anforderung zum Abrufen des Tabelleninhalts aufgefüllt.
Zurückgeben aller Daten
Daten werden über eine IAsyncEnumerablezurückgegeben:
var enumerable = remoteTable.ToAsyncEnumerable();
await foreach (var item in enumerable)
{
// Process each item
}
Verwenden Sie eine der folgenden Beendigungsklauseln, um die IAsyncEnumerable<T>
in eine andere Auflistung zu konvertieren:
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();
Im Hintergrund behandelt die Remotetabelle die Auslagerung des Ergebnisses für Sie. Alle Elemente werden unabhängig davon zurückgegeben, wie viele serverseitige Anforderungen erforderlich sind, um die Abfrage zu erfüllen. Diese Elemente sind auch für Abfrageergebnisse verfügbar (z. B. remoteTable.Where(m => m.Rating == "R")
).
Das Datensynchronisierungsframework bietet auch ConcurrentObservableCollection<T>
– eine threadsichere feststellbare Sammlung. Diese Klasse kann im Kontext von Benutzeroberflächenanwendungen verwendet werden, die normalerweise ObservableCollection<T>
zum Verwalten einer Liste verwenden würden (z. B. Xamarin Forms oder MAUI-Listen). Sie können eine ConcurrentObservableCollection<T>
direkt aus einer Tabelle oder Abfrage löschen und laden:
var collection = new ConcurrentObservableCollection<T>();
await remoteTable.ToObservableCollection(collection);
Wenn Sie .ToObservableCollection(collection)
verwenden, wird das CollectionChanged
-Ereignis einmal für die gesamte Auflistung und nicht für einzelne Elemente ausgelöst, was zu einer schnelleren Neuerfassungszeit führt.
Die ConcurrentObservableCollection<T>
verfügt auch über prädikatgesteuerte Änderungen:
// 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);
Prädikatgesteuerte Änderungen können in Ereignishandlern verwendet werden, wenn der Index des Elements im Voraus nicht bekannt ist.
Filtern von Daten
Sie können eine .Where()
Klausel verwenden, um Daten zu filtern. Zum Beispiel:
var items = await remoteTable.Where(x => !x.IsComplete).ToListAsync();
Das Filtern erfolgt auf dem Dienst vor dem IAsyncEnumerable und auf dem Client nach dem IAsyncEnumerable-Element. Zum Beispiel:
var items = (await remoteTable.Where(x => !x.IsComplete).ToListAsync()).Where(x => x.Title.StartsWith("The"));
Die erste .Where()
Klausel (nur unvollständige Elemente zurückgeben) wird für den Dienst ausgeführt, während die zweite .Where()
-Klausel (beginnend mit "The") auf dem Client ausgeführt wird.
Die Where
-Klausel unterstützt Vorgänge, die in die OData-Teilmenge übersetzt werden. Zu den Vorgängen gehören:
- Relationale Operatoren (
==
,!=
,<
,<=
,>
,>=
), - Arithmetische Operatoren (
+
,-
,/
,*
,%
), - Zahlengenauigkeit (
Math.Floor
,Math.Ceiling
), - Zeichenfolgenfunktionen (
Length
,Substring
,Replace
,IndexOf
,Equals
,StartsWith
,EndsWith
) (nur Ordinal- und invarianten Kulturen), - Datumseigenschaften (
Year
,Month
,Day
,Hour
,Minute
,Second
), - Zugreifen auf Eigenschaften eines Objekts und
- Ausdrücke, die eine dieser Vorgänge kombinieren.
Sortieren von Daten
Verwenden Sie .OrderBy()
, .OrderByDescending()
, .ThenBy()
und .ThenByDescending()
mit einem Eigenschaftenaccessor, um Daten zu sortieren.
var items = await remoteTable.OrderBy(x => x.IsComplete).ThenBy(x => x.Title).ToListAsync();
Die Sortierung erfolgt durch den Dienst. Sie können in keiner Sortierklausel einen Ausdruck angeben. Wenn Sie nach einem Ausdruck sortieren möchten, verwenden Sie die clientseitige Sortierung:
var items = await remoteTable.ToListAsync().OrderBy(x => x.Title.ToLowerCase());
Auswählen von Eigenschaften
Sie können eine Teilmenge von Daten aus dem Dienst zurückgeben:
var items = await remoteTable.Select(x => new { x.Id, x.Title, x.IsComplete }).ToListAsync();
Zurückgeben einer Datenseite
Sie können eine Teilmenge des Datasets mithilfe von .Skip()
zurückgeben und .Take()
, um Paging zu implementieren:
var pageOfItems = await remoteTable.Skip(100).Take(10).ToListAsync();
In einer realen App können Sie Abfragen verwenden, die dem vorherigen Beispiel mit einem Pager-Steuerelement oder einer vergleichbaren Benutzeroberfläche ähneln, um zwischen Seiten zu navigieren.
Alle bisher beschriebenen Funktionen sind additiv, damit wir sie weiter verketten können. Jeder verkettete Aufruf wirkt sich mehr auf die Abfrage aus. Ein weiteres Beispiel:
var query = todoTable
.Where(todoItem => todoItem.Complete == false)
.Select(todoItem => todoItem.Text)
.Skip(3).
.Take(3);
List<string> items = await query.ToListAsync();
Nachschlagen von Remotedaten nach ID
Die GetItemAsync
-Funktion kann verwendet werden, um Objekte aus der Datenbank mit einer bestimmten ID nachzuschlagen.
TodoItem item = await remoteTable.GetItemAsync("37BBF396-11F0-4B39-85C8-B319C729AF6D");
Wenn das element, das Sie abrufen möchten, vorläufig gelöscht wurde, müssen Sie den parameter includeDeleted
verwenden:
// 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);
Einfügen von Daten auf dem Remoteserver
Alle Clienttypen müssen ein Element mit dem Namen IDenthalten, das standardmäßig eine Zeichenfolge ist. Diese ID- ist erforderlich, um CRUD-Vorgänge und offline zu synchronisieren. Der folgende Code veranschaulicht, wie die InsertItemAsync
-Methode verwendet wird, um neue Zeilen in eine Tabelle einzufügen. Der Parameter enthält die Daten, die als .NET-Objekt eingefügt werden sollen.
var item = new TodoItem { Title = "Text", IsComplete = false };
await remoteTable.InsertItemAsync(item);
// Note that item.Id will now be set
Wenn ein eindeutiger benutzerdefinierter ID-Wert während eines Einfügevorgangs nicht im item
enthalten ist, generiert der Server eine ID. Sie können die generierte ID abrufen, indem Sie das Objekt überprüfen, nachdem der Aufruf zurückgegeben wurde.
Aktualisieren von Daten auf dem Remoteserver
Im folgenden Code wird veranschaulicht, wie Sie mit der ReplaceItemAsync
-Methode einen vorhandenen Datensatz mit derselben ID mit neuen Informationen aktualisieren.
// In this example, we assume the item has been created from the InsertItemAsync sample
item.IsComplete = true;
await remoteTable.ReplaceItemAsync(todoItem);
Löschen von Daten auf dem Remoteserver
Der folgende Code veranschaulicht, wie die DeleteItemAsync
-Methode zum Löschen einer vorhandenen Instanz verwendet wird.
// In this example, we assume the item has been created from the InsertItemAsync sample
await todoTable.DeleteItemAsync(item);
Konfliktauflösung und optimistische Parallelität
Zwei oder mehr Clients können Gleichzeitig Änderungen an demselben Element schreiben. Ohne Konflikterkennung würde der letzte Schreibvorgang alle vorherigen Updates überschreiben. optimistische Parallelitätssteuerung davon aus, dass jede Transaktion Commit ausführen kann und daher keine Ressourcensperre verwendet. Das optimistische Parallelitätssteuerelement überprüft, ob keine andere Transaktion die Daten geändert hat, bevor ein Commit für die Daten ausgeführt wird. Wenn die Daten geändert wurden, wird die Transaktion zurückgesetzt.
Azure Mobile Apps unterstützt optimistische Parallelitätssteuerung, indem Änderungen an den einzelnen Elementen mithilfe der version
Systemeigenschaftenspalte nachverfolgt werden, die für jede Tabelle in Ihrem Mobile App-Back-End definiert ist. Jedes Mal, wenn ein Datensatz aktualisiert wird, legt Mobile Apps die version
-Eigenschaft für diesen Datensatz auf einen neuen Wert fest. Bei jeder Aktualisierungsanforderung wird die version
Eigenschaft des Datensatzes, der in der Anforderung enthalten ist, mit der gleichen Eigenschaft für den Datensatz auf dem Server verglichen. Wenn die mit der Anforderung übergebene Version nicht mit dem Back-End übereinstimmt, löst die Clientbibliothek eine DatasyncConflictException<T>
Ausnahme aus. Der in der Ausnahme enthaltene Typ ist der Datensatz aus dem Back-End, der die Serverversion des Datensatzes enthält. Die Anwendung kann diese Informationen dann verwenden, um zu entscheiden, ob die Updateanforderung erneut mit dem richtigen version
Wert aus dem Back-End ausgeführt werden soll, um Änderungen zu übernehmen.
Optimistische Parallelität wird bei Verwendung des DatasyncClientData
Basisobjekts automatisch aktiviert.
Zusätzlich zur Aktivierung optimistischer Parallelität müssen Sie auch die DatasyncConflictException<T>
Ausnahme im Code abfangen. Lösen Sie den Konflikt aus, indem Sie den richtigen version
auf den aktualisierten Datensatz anwenden und dann den Anruf mit dem aufgelösten Datensatz wiederholen. Der folgende Code zeigt, wie ein Schreibkonflikt gelöst wird, nachdem ein Fehler erkannt wurde:
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();
}
Arbeiten mit Offlinetabellen
Offlinetabellen verwenden einen lokalen SQLite-Speicher, um Daten zur Verwendung im Offlinemodus zu speichern. Alle Tabellenvorgänge werden für den lokalen SQLite-Speicher anstelle des Remoteserverspeichers ausgeführt. Stellen Sie sicher, dass Sie jedem Plattformprojekt und allen freigegebenen Projekten die Microsoft.Datasync.Client.SQLiteStore
hinzufügen.
Bevor ein Tabellenverweis erstellt werden kann, muss der lokale Speicher vorbereitet werden:
var store = new OfflineSQLiteStore(Constants.OfflineConnectionString);
store.DefineTable<TodoItem>();
Nachdem der Speicher definiert wurde, können Sie den Client erstellen:
var options = new DatasyncClientOptions
{
OfflineStore = store
};
var client = new DatasyncClient("MOBILE_URL", options);
Schließlich müssen Sie sicherstellen, dass die Offlinefunktionen initialisiert werden:
await client.InitializeOfflineStoreAsync();
Die Store-Initialisierung erfolgt normalerweise unmittelbar nach dem Erstellen des Clients. Die OfflineConnectionString- ist ein URI, der zum Angeben des Speicherorts der SQLite-Datenbank und der Optionen zum Öffnen der Datenbank verwendet wird. Weitere Informationen finden Sie unter URI Filenames in SQLite.
- Um einen Speichercache zu verwenden, verwenden Sie
file:inmemory.db?mode=memory&cache=private
. - Um eine Datei zu verwenden, verwenden Sie
file:/path/to/file.db
Sie müssen den absoluten Dateinamen für die Datei angeben. Wenn Sie Xamarin verwenden, können Sie die Xamarin Essentials File System Helpers verwenden, um einen Pfad zu erstellen: Beispiel:
var dbPath = $"{Filesystem.AppDataDirectory}/todoitems.db";
var store = new OfflineSQLiteStore($"file:/{dbPath}?mode=rwc");
Wenn Sie MAUI verwenden, können Sie die MAUI File System Helpers verwenden, um einen Pfad zu erstellen: Beispiel:
var dbPath = $"{Filesystem.AppDataDirectory}/todoitems.db";
var store = new OfflineSQLiteStore($"file:/{dbPath}?mode=rwc");
Erstellen einer Offlinetabelle
Mithilfe der GetOfflineTable<T>
-Methode kann ein Tabellenverweis abgerufen werden:
IOfflineTable<TodoItem> table = client.GetOfflineTable<TodoItem>();
Wie bei der Remotetabelle können Sie auch eine schreibgeschützte Offlinetabelle verfügbar machen:
IReadOnlyOfflineTable<TodoItem> table = client.GetOfflineTable<TodoItem>();
Sie müssen sich nicht authentifizieren, um eine Offlinetabelle zu verwenden. Sie müssen sich nur authentifizieren, wenn Sie mit dem Back-End-Dienst kommunizieren.
Synchronisieren einer Offlinetabelle
Offlinetabellen werden standardmäßig nicht mit dem Back-End synchronisiert. Die Synchronisierung wird in zwei Teile aufgeteilt. Sie können Änderungen separat vom Herunterladen neuer Elemente übertragen. Zum Beispiel:
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"]);
}
}
}
Standardmäßig verwenden alle Tabellen die inkrementelle Synchronisierung – nur neue Datensätze werden abgerufen. Ein Datensatz ist für jede eindeutige Abfrage enthalten (generiert durch Erstellen eines MD5-Hashs der OData-Abfrage).
Anmerkung
Das erste Argument für PullItemsAsync
ist die OData-Abfrage, die angibt, welche Datensätze auf das Gerät übertragen werden sollen. Es ist besser, den Dienst so zu ändern, dass nur datensätze zurückgegeben werden, die für den Benutzer spezifisch sind, anstatt komplexe Abfragen auf clientseitiger Seite zu erstellen.
Die vom PullOptions
-Objekt definierten Optionen müssen im Allgemeinen nicht festgelegt werden. Zu den Optionen gehören:
-
PushOtherTables
– wenn dieser Wert auf "true" festgelegt ist, werden alle Tabellen verschoben. -
QueryId
– eine bestimmte Abfrage-ID, die anstelle der generierten id verwendet werden soll. -
WriteDeltaTokenInterval
– wie oft das Delta-Token zum Nachverfolgen der inkrementellen Synchronisierung verwendet wird.
Das SDK führt vor dem Abrufen von Datensätzen eine implizite PushAsync()
aus.
Die Konfliktbehandlung erfolgt in einer PullAsync()
-Methode. Behandeln Sie Konflikte auf die gleiche Weise wie Onlinetabellen. Der Konflikt wird erzeugt, wenn PullAsync()
anstelle des Einfügens, Aktualisierens oder Löschens aufgerufen wird. Wenn mehrere Konflikte auftreten, werden sie in einem einzigen PushFailedException
gebündelt. Behandeln Sie jeden Fehler separat.
Pushänderungen für alle Tabellen
Um alle Änderungen an den Remoteserver zu übertragen, verwenden Sie Folgendes:
await client.PushTablesAsync();
Um Änderungen für eine Teilmenge von Tabellen zu übertragen, stellen Sie eine IEnumerable<string>
für die PushTablesAsync()
-Methode bereit:
var tablesToPush = new string[] { "TodoItem", "Notes" };
await client.PushTables(tablesToPush);
Verwenden Sie die eigenschaft client.PendingOperations
, um die Anzahl der Vorgänge zu lesen, die darauf warten, an den Remotedienst zu übertragen. Diese Eigenschaft wird null
, wenn kein Offlinespeicher konfiguriert wurde.
Ausführen komplexer SQLite-Abfragen
Wenn Sie komplexe SQL-Abfragen für die Offlinedatenbank ausführen müssen, können Sie dies mithilfe der ExecuteQueryAsync()
-Methode tun. Um beispielsweise eine SQL JOIN
-Anweisung auszuführen, definieren Sie eine JObject
, die die Struktur des Rückgabewerts anzeigt, und verwenden Sie dann 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.
Die Definition ist ein Satz von Schlüssel/Werten. Die Schlüssel müssen mit den Feldnamen übereinstimmen, die von der SQL-Abfrage zurückgegeben werden, und die Werte müssen der Standardwert des erwarteten Typs sein. Verwenden Sie 0L
für Zahlen (lang), false
für Booleane und string.Empty
für alles andere.
SQLite verfügt über einen restriktiven Satz unterstützter Typen. Datum/Uhrzeiten werden seit der Epoche als Anzahl von Millisekunden gespeichert, um Vergleiche zu ermöglichen.
Authentifizieren von Benutzern
Mit Azure Mobile Apps können Sie einen Authentifizierungsanbieter für die Behandlung von Authentifizierungsaufrufen generieren. Geben Sie beim Erstellen des Dienstclients den Authentifizierungsanbieter an:
AuthenticationProvider authProvider = GetAuthenticationProvider();
var client = new DatasyncClient("APP_URL", authProvider);
Wenn die Authentifizierung erforderlich ist, wird der Authentifizierungsanbieter aufgerufen, um das Token abzurufen. Ein generischer Authentifizierungsanbieter kann sowohl für die autorisierungsbasierte Authentifizierung als auch für die autorisierungsbasierte Authentifizierung und autorisierungsbasierte Authentifizierung verwendet werden. Verwenden Sie das folgende Modell:
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 */"
};
}
Authentifizierungstoken werden im Arbeitsspeicher zwischengespeichert (nie auf Das Gerät geschrieben) und bei Bedarf aktualisiert.
Verwenden der Microsoft Identity Platform
Mit der Microsoft Identity Platform können Sie problemlos in Microsoft Entra ID integriert werden. In den Schnellstartlernprogrammen finden Sie ein vollständiges Lernprogramm zum Implementieren der Microsoft Entra-Authentifizierung. Der folgende Code zeigt ein Beispiel für das Abrufen des Zugriffstokens:
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;
}
}
Weitere Informationen zur Integration der Microsoft Identity Platform mit ASP.NET 6 finden Sie in der dokumentation Microsoft Identity Platform.
Verwenden von Xamarin Essentials oder MAUI WebAuthenticator
Für die Azure App Service-Authentifizierung können Sie den Xamarin Essentials WebAuthenticator oder die MAUI WebAuthenticator- verwenden, um ein Token abzurufen:
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
};
}
Die UserId
und DisplayName
sind bei Verwendung der Azure App Service-Authentifizierung nicht direkt verfügbar. Verwenden Sie stattdessen einen faulen Anforderer, um die Informationen vom /.auth/me
-Endpunkt abzurufen:
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);
}
}
Erweiterte Themen
Löschen von Entitäten in der lokalen Datenbank
Unter normalem Vorgang ist das Löschen von Entitäten nicht erforderlich. Der Synchronisierungsprozess entfernt gelöschte Entitäten und verwaltet die erforderlichen Metadaten für lokale Datenbanktabellen. Es gibt jedoch Situationen, in denen das Löschen von Entitäten innerhalb der Datenbank hilfreich ist. Ein solches Szenario ist, wenn Sie eine große Anzahl von Entitäten löschen müssen, und es ist effizienter, Daten aus der Tabelle lokal zu löschen.
Verwenden Sie table.PurgeItemsAsync()
, um Datensätze aus einer Tabelle zu löschen:
var query = table.CreateQuery();
var purgeOptions = new PurgeOptions();
await table.PurgeItermsAsync(query, purgeOptions, cancellationToken);
Die Abfrage identifiziert die Entitäten, die aus der Tabelle entfernt werden sollen. Identifizieren Sie die Entitäten, die mithilfe von LINQ gelöscht werden sollen:
var query = table.CreateQuery().Where(m => m.Archived == true);
Die PurgeOptions
-Klasse stellt Einstellungen zum Ändern des Bereinigungsvorgangs bereit:
-
DiscardPendingOperations
verwirft alle ausstehenden Vorgänge für die Tabelle, die sich in der Betriebswarteschlange befinden, die auf das Senden an den Server wartet. -
QueryId
gibt eine Abfrage-ID an, die verwendet wird, um das delta-Token zu identifizieren, das für den Vorgang verwendet werden soll. -
TimestampUpdatePolicy
gibt an, wie das Delta-Token am Ende des Bereinigungsvorgangs angepasst werden soll:-
TimestampUpdatePolicy.NoUpdate
gibt an, dass das Delta-Token nicht aktualisiert werden darf. -
TimestampUpdatePolicy.UpdateToLastEntity
gibt an, dass das Delta-Token auf das FeldupdatedAt
für die letzte in der Tabelle gespeicherte Entität aktualisiert werden soll. -
TimestampUpdatePolicy.UpdateToNow
gibt an, dass das Delta-Token auf das aktuelle Datum/die aktuelle Uhrzeit aktualisiert werden soll. -
TimestampUpdatePolicy.UpdateToEpoch
gibt an, dass das Delta-Token zurückgesetzt werden soll, um alle Daten zu synchronisieren.
-
Verwenden Sie denselben QueryId
Wert, den Sie beim Aufrufen von table.PullItemsAsync()
zum Synchronisieren von Daten verwendet haben. Die QueryId
gibt das Deltatoken an, das aktualisiert werden soll, wenn die Bereinigung abgeschlossen ist.
Anpassen von Anforderungsheadern
Um Ihr spezifisches App-Szenario zu unterstützen, müssen Sie möglicherweise die Kommunikation mit dem Mobilen App-Back-End anpassen. Sie können beispielsweise jeder ausgehenden Anforderung einen benutzerdefinierten Header hinzufügen oder Antwortstatuscodes ändern, bevor Sie an den Benutzer zurückkehren. Verwenden Sie wie im folgenden Beispiel einen benutzerdefinierten DelegatingHandler:
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;
}
}
Aktivieren der Anforderungsprotokollierung
Sie können auch einen DelegatingHandler verwenden, um die Anforderungsprotokollierung hinzuzufügen:
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;
}
}
Überwachen von Synchronisierungsereignissen
Wenn ein Synchronisierungsereignis auftritt, wird das Ereignis im client.SynchronizationProgress
Ereignisdelegat veröffentlicht. Die Ereignisse können verwendet werden, um den Fortschritt des Synchronisierungsprozesses zu überwachen. Definieren Sie einen Synchronisierungsereignishandler wie folgt:
client.SynchronizationProgress += (sender, args) => {
// args is of type SynchronizationEventArgs
};
Der SynchronizationEventArgs
Typ ist wie folgt definiert:
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; }
}
Die Eigenschaften innerhalb args
sind entweder null
oder -1
, wenn die Eigenschaft für das Synchronisierungsereignis nicht relevant ist.