Verwenden der Azure Mobile Apps v4.2.0-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 Windows-Anwendungen (WPF, UWP) oder Xamarin(Native oder Forms). Wenn Sie mit Azure Mobile Apps noch nicht vertraut sind, sollten Sie zuerst das Lernprogramm Schnellstart für Xamarin.Forms abschließen.
Warnung
In diesem Artikel werden Informationen für die v4.2.0-Bibliotheksversion behandelt, die von der v5.0.0-Bibliothek ersetzt wird. Die aktuellsten Informationen finden Sie im Artikel zur neuesten Version
Unterstützte Plattformen
Die .NET-Clientbibliothek unterstützt .NET Standard 2.0 und die folgenden Plattformen:
- Xamarin.Android von API-Ebene 19 bis ZUR API-Ebene 30.
- Xamarin.iOS Version 8.0 bis 14.3.
- Universelle Windows-Plattform builds 16299 und höher.
- Jede .NET Standard 2.0-Anwendung.
Die "Serverfluss"-Authentifizierung verwendet ein WebView für die dargestellte Benutzeroberfläche und ist möglicherweise nicht auf jeder Plattform verfügbar. Wenn sie nicht verfügbar ist, müssen Sie eine "Clientfluss"-Authentifizierung bereitstellen. Diese Clientbibliothek eignet sich nicht für Überwachungs- oder IoT-Formfaktoren bei der Verwendung der Authentifizierung.
Setup und Voraussetzungen
Es wird davon ausgegangen, dass Sie Ihr Azure Mobile Apps-Back-End-Projekt bereits erstellt und veröffentlicht haben, das mindestens eine Tabelle enthält. Im in diesem Thema verwendeten Code wird die Tabelle TodoItem
benannt und verfügt über eine Zeichenfolge Id
und Text
Felder und eine boolesche Complete
Spalte. Diese Tabelle ist dieselbe Tabelle, die beim Abschließen der Schnellstartanleitungerstellt wird.
Der entsprechende typierte clientseitige Typ in C# ist diese Klasse:
public class TodoItem
{
public string Id { get; set; }
[JsonProperty(PropertyName = "text")]
public string Text { get; set; }
[JsonProperty(PropertyName = "complete")]
public bool Complete { get; set; }
}
Die JsonPropertyAttribute- wird verwendet, um die PropertyName- Zuordnung zwischen dem Clientfeld und dem Tabellenfeld zu definieren.
Informationen zum Erstellen von Tabellen in Ihrem Mobile Apps-Back-End finden Sie im Thema .NET Server SDK dem Thema Node.js Server SDK.
Installieren des verwalteten Client-SDK-Pakets
Klicken Sie mit der rechten Maustaste auf Ihr Projekt, drücken Sie NuGet-Pakete verwalten, suchen Sie nach dem Microsoft.Azure.Mobile.Client
-Paket, und drücken Sie dann Installieren. Installieren Sie das Microsoft.Azure.Mobile.Client.SQLiteStore
Paket auch für Offlinefunktionen.
Erstellen des Azure Mobile Apps-Clients
Der folgende Code erstellt das MobileServiceClient--Objekt, das für den Zugriff auf Ihr Mobile App-Back-End verwendet wird.
var client = new MobileServiceClient("MOBILE_APP_URL");
Ersetzen Sie im vorherigen Code MOBILE_APP_URL
durch die URL des App Service-Back-Ends. Das MobileServiceClient
-Objekt sollte ein Singleton sein.
Arbeiten mit Tabellen
Im folgenden Abschnitt wird erläutert, wie Datensätze gesucht und abgerufen und die Daten in der Tabelle geändert werden. Die folgenden Themen werden behandelt:
- Erstellen eines Tabellenverweises
- Abfragedaten
- Vom Filter zurückgegebene Daten
- Zurückgegebene Daten sortieren
- Zurückgeben von Daten auf Seiten
- Auswählen bestimmter Spalten
- Nachschlagen eines Datensatzes nach ID-
- Ausführen von nicht typisierten Abfragen
- Einfügen von Daten
- Aktualisieren von Daten
- Löschen von Daten
- Konfliktlösung und optimistische Parallelität
- Binden von Daten an eine Windows-Benutzeroberfläche
- Ändern des Seitenformats
Erstellen eines Tabellenverweises
Der gesamte Code, der auf Daten in einer Back-End-Tabelle zugreift oder ändert, ruft Funktionen für das MobileServiceTable
-Objekt auf. Rufen Sie einen Verweis auf die Tabelle ab, indem Sie die GetTable--Methode wie folgt aufrufen:
IMobileServiceTable<TodoItem> todoTable = client.GetTable<TodoItem>();
Das zurückgegebene Objekt verwendet das typierte Serialisierungsmodell. Ein nicht typisiertes Serialisierungsmodell wird ebenfalls unterstützt. Im folgenden Beispiel wird ein Verweis auf eine nicht typisierte Tabelleerstellt:
// Get an untyped table reference
IMobileServiceTable untypedTodoTable = client.GetTable("TodoItem");
In nicht typisierten Abfragen müssen Sie die zugrunde liegende OData-Abfragezeichenfolge angeben.
Abfragen von Daten aus Ihrer mobilen App
In diesem Abschnitt wird beschrieben, wie Sie Abfragen an das Mobile App-Back-End ausgeben, das die folgenden Funktionen enthält:
- Vom Filter zurückgegebene Daten
- Zurückgegebene Daten sortieren
- Zurückgeben von Daten auf Seiten
- Auswählen bestimmter Spalten
- Nachschlagen eines Datensatzes nach ID-
Anmerkung
Eine servergesteuerte Seitengröße wird erzwungen, um zu verhindern, dass alle Zeilen zurückgegeben werden. Durch das Paging werden Standardanforderungen für große Datasets von negativen Auswirkungen auf den Dienst beibehalten. Um mehr als 50 Zeilen zurückzugeben, verwenden Sie die methode Skip
und Take
, wie in Rückgabedaten in Seitenbeschrieben.
Zurückgegebene Daten filtern
Der folgende Code veranschaulicht, wie Daten gefiltert werden, indem eine Where
-Klausel in eine Abfrage eingeschlossen wird. Es gibt alle Elemente aus todoTable
zurück, deren Complete
Eigenschaft gleich false
ist. Die Where-Funktion wendet ein Zeilenfilter-Prädikat auf die Abfrage für die Tabelle an.
// This query filters out completed TodoItems and items without a timestamp.
List<TodoItem> items = await todoTable
.Where(todoItem => todoItem.Complete == false)
.ToListAsync();
Sie können den URI der an das Back-End gesendeten Anforderung mithilfe von Nachrichtenüberprüfungssoftware anzeigen, z. B. Browserentwicklertools oder Fiddler. Wenn Sie den Anforderungs-URI betrachten, beachten Sie, dass die Abfragezeichenfolge geändert wird:
GET /tables/todoitem?$filter=(complete+eq+false) HTTP/1.1
Diese OData-Anforderung wird in eine SQL-Abfrage vom Server SDK übersetzt:
SELECT *
FROM TodoItem
WHERE ISNULL(complete, 0) = 0
Die Funktion, die an die Where
-Methode übergeben wird, kann eine beliebige Anzahl von Bedingungen aufweisen.
// This query filters out completed TodoItems where Text isn't null
List<TodoItem> items = await todoTable
.Where(todoItem => todoItem.Complete == false && todoItem.Text != null)
.ToListAsync();
In diesem Beispiel wird eine SQL-Abfrage vom Server SDK übersetzt:
SELECT *
FROM TodoItem
WHERE ISNULL(complete, 0) = 0
AND ISNULL(text, 0) = 0
Diese Abfrage kann auch in mehrere Klauseln aufgeteilt werden:
List<TodoItem> items = await todoTable
.Where(todoItem => todoItem.Complete == false)
.Where(todoItem => todoItem.Text != null)
.ToListAsync();
Die beiden Methoden sind gleichwertig und können austauschbar verwendet werden. Die frühere Option zum Verketten mehrerer Prädikate in einer Abfrage ist kompakter und empfohlener.
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
,StartsWith
,EndsWith
), - Datumseigenschaften (
Year
,Month
,Day
,Hour
,Minute
,Second
), - Zugreifen auf Eigenschaften eines Objekts und
- Ausdrücke, die eine dieser Vorgänge kombinieren.
Wenn Sie überlegen, was das Server SDK unterstützt, können Sie die OData v3 Documentationberücksichtigen.
Zurückgegebene Daten sortieren
Der folgende Code veranschaulicht, wie Daten sortiert werden, indem eine OrderBy- oder OrderByDescending- funktion in die Abfrage eingeschlossen wird. Es gibt Elemente aus todoTable
nach dem feld Text
aufsteigend sortiert zurück.
// Sort items in ascending order by Text field
MobileServiceTableQuery<TodoItem> query = todoTable
.OrderBy(todoItem => todoItem.Text)
List<TodoItem> items = await query.ToListAsync();
// Sort items in descending order by Text field
MobileServiceTableQuery<TodoItem> query = todoTable
.OrderByDescending(todoItem => todoItem.Text)
List<TodoItem> items = await query.ToListAsync();
Zurückgeben von Daten auf Seiten
Standardmäßig gibt das Back-End nur die ersten 50 Zeilen zurück. Sie können die Anzahl der zurückgegebenen Zeilen erhöhen, indem Sie die Take-Methode aufrufen. Verwenden Sie Take
zusammen mit der Skip-Methode, um eine bestimmte "Seite" des gesamt von der Abfrage zurückgegebenen Datasets anzufordern. Die folgende Abfrage gibt bei Ausführung die drei obersten Elemente in der Tabelle zurück.
// Define a filtered query that returns the top 3 items.
MobileServiceTableQuery<TodoItem> query = todoTable.Take(3);
List<TodoItem> items = await query.ToListAsync();
Die folgende überarbeitete Abfrage überspringt die ersten drei Ergebnisse und gibt die nächsten drei Ergebnisse zurück. Diese Abfrage erzeugt die zweite "Seite" von Daten, wobei die Seitengröße drei Elemente ist.
// Define a filtered query that skips the top 3 items and returns the next 3 items.
MobileServiceTableQuery<TodoItem> query = todoTable.Skip(3).Take(3);
List<TodoItem> items = await query.ToListAsync();
Die IncludeTotalCount- Methode fordert die Gesamtanzahl für alle der zurückgegebenen Datensätze an, wobei alle angegebenen Paging/Limit-Klauseln ignoriert werden:
query = query.IncludeTotalCount();
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.
Anmerkung
Um den Grenzwert von 50 Zeilen in einem Mobilen App-Back-End außer Kraft zu setzen, müssen Sie auch die EnableQueryAttribute- auf die öffentliche GET-Methode anwenden und das Pagingverhalten angeben. Bei Anwendung auf die Methode legt Folgendes die maximal zurückgegebenen Zeilen auf 1000 fest:
[EnableQuery(MaxTop=1000)]
Auswählen bestimmter Spalten
Sie können angeben, welche Eigenschaften in die Ergebnisse einbezogen werden sollen, indem Sie ihrer Abfrage eine Select-Klausel hinzufügen. Mit dem folgenden Code wird beispielsweise gezeigt, wie sie nur ein Feld auswählen und auch mehrere Felder auswählen und formatieren:
// Select one field -- just the Text
MobileServiceTableQuery<TodoItem> query = todoTable
.Select(todoItem => todoItem.Text);
List<string> items = await query.ToListAsync();
// Select multiple fields -- both Complete and Text info
MobileServiceTableQuery<TodoItem> query = todoTable
.Select(todoItem => string.Format("{0} -- {1}",
todoItem.Text.PadRight(30), todoItem.Complete ?
"Now complete!" : "Incomplete!"));
List<string> items = await query.ToListAsync();
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:
MobileServiceTableQuery<TodoItem> query = todoTable
.Where(todoItem => todoItem.Complete == false)
.Select(todoItem => todoItem.Text)
.Skip(3).
.Take(3);
List<string> items = await query.ToListAsync();
Nachschlagen von Daten nach ID
Die LookupAsync--Funktion kann verwendet werden, um Objekte aus der Datenbank mit einer bestimmten ID nachzuschlagen.
// This query filters out the item with the ID of 37BBF396-11F0-4B39-85C8-B319C729AF6D
TodoItem item = await todoTable.LookupAsync("37BBF396-11F0-4B39-85C8-B319C729AF6D");
Ausführen von nicht typisierten Abfragen
Wenn Sie eine Abfrage mit einem nicht typisierten Tabellenobjekt ausführen, müssen Sie die OData-Abfragezeichenfolge explizit angeben, indem Sie ReadAsync-aufrufen, wie im folgenden Beispiel gezeigt:
// Lookup untyped data using OData
JToken untypedItems = await untypedTodoTable.ReadAsync("$filter=complete eq 0&$orderby=text");
Sie erhalten JSON-Werte, die Sie wie einen Eigenschaftenbehälter verwenden können. Weitere Informationen zu JToken
und Newtonsoft Json finden Sie auf der Newtonsoft JSON--Website.
Einfügen von Daten
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 InsertAsync--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.
await todoTable.InsertAsync(todoItem);
Wenn während eines Einfügens kein eindeutiger benutzerdefinierter ID-Wert in der todoItem
enthalten ist, wird vom Server eine GUID generiert. Sie können die generierte ID abrufen, indem Sie das Objekt überprüfen, nachdem der Aufruf zurückgegeben wurde.
Um nicht typisierte Daten einzufügen, können Sie Json.NET nutzen:
JObject jo = new JObject();
jo.Add("Text", "Hello World");
jo.Add("Complete", false);
var inserted = await table.InsertAsync(jo);
Nachfolgend sehen Sie ein Beispiel für die Verwendung einer E-Mail-Adresse als eindeutige Zeichenfolgen-ID:
JObject jo = new JObject();
jo.Add("id", "myemail@emaildomain.com");
jo.Add("Text", "Hello World");
jo.Add("Complete", false);
var inserted = await table.InsertAsync(jo);
Arbeiten mit ID-Werten
Mobile Apps unterstützt eindeutige benutzerdefinierte Zeichenfolgenwerte für die ID der Tabelle Spalte. Mit einem Zeichenfolgenwert können Anwendungen benutzerdefinierte Werte wie E-Mail-Adressen oder Benutzernamen für die ID verwenden. Zeichenfolgen-IDs bieten Ihnen die folgenden Vorteile:
- IDs werden generiert, ohne einen Roundtrip zur Datenbank vorzunehmen.
- Datensätze können einfacher aus verschiedenen Tabellen oder Datenbanken zusammengeführt werden.
- IDs-Werte können besser in die Logik einer Anwendung integriert werden.
Wenn kein Zeichenfolgen-ID-Wert für einen eingefügten Datensatz festgelegt wird, generiert das Mobile App-Back-End einen eindeutigen Wert für die ID. Sie können die Guid.NewGuid Methode verwenden, um eigene ID-Werte entweder auf dem Client oder im Back-End zu generieren.
JObject jo = new JObject();
jo.Add("id", Guid.NewGuid().ToString("N"));
Aktualisieren von Daten
Im folgenden Code wird veranschaulicht, wie Sie mithilfe der UpdateAsync Methode einen vorhandenen Datensatz mit derselben ID mit neuen Informationen aktualisieren. Der Parameter enthält die Daten, die als .NET-Objekt aktualisiert werden sollen.
await todoTable.UpdateAsync(todoItem);
Um nicht typisierte Daten zu aktualisieren, können Sie Newtonsoft JSON- wie folgt nutzen:
JObject jo = new JObject();
jo.Add("id", "37BBF396-11F0-4B39-85C8-B319C729AF6D");
jo.Add("Text", "Hello World");
jo.Add("Complete", false);
var inserted = await table.UpdateAsync(jo);
Ein id
-Feld muss beim Aktualisieren angegeben werden. Das Back-End verwendet das feld id
, um zu identifizieren, welche Zeile aktualisiert werden soll. Das feld id
kann aus dem Ergebnis des InsertAsync
Aufrufs abgerufen werden. Ein ArgumentException
wird ausgelöst, wenn Sie versuchen, ein Element zu aktualisieren, ohne den id
Wert bereitzustellen.
Löschen von Daten
Der folgende Code veranschaulicht, wie die DeleteAsync--Methode zum Löschen einer vorhandenen Instanz verwendet wird. Die Instanz wird durch das feld id
für die todoItem
identifiziert.
await todoTable.DeleteAsync(todoItem);
Um nicht eingegebene Daten zu löschen, können Sie Json.NET wie folgt nutzen:
JObject jo = new JObject();
jo.Add("id", "37BBF396-11F0-4B39-85C8-B319C729AF6D");
await table.DeleteAsync(jo);
Wenn Sie eine Löschanforderung vornehmen, muss eine ID angegeben werden. Andere Eigenschaften werden nicht an den Dienst übergeben oder beim Dienst ignoriert. Das Ergebnis eines DeleteAsync
Anrufs wird in der Regel null
. Die zu übergebende ID kann aus dem Ergebnis des InsertAsync
Aufrufs abgerufen werden. Ein MobileServiceInvalidOperationException
wird ausgelöst, wenn Sie versuchen, ein Element zu löschen, ohne das feld id
anzugeben.
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 commiten kann und daher keine Ressourcensperre verwendet. Vor dem Commit einer Transaktion überprüft das optimistische Parallelitätssteuerelement, dass keine andere Transaktion die Daten geändert hat. Wenn die Daten geändert wurden, wird die Committransaktion zurückgesetzt.
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 MobileServicePreconditionFailedException<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.
Definieren Sie eine Spalte in der Tabellenklasse für die version
Systemeigenschaft, um eine optimistische Parallelität zu ermöglichen. Zum Beispiel:
public class TodoItem
{
public string Id { get; set; }
[JsonProperty(PropertyName = "text")]
public string Text { get; set; }
[JsonProperty(PropertyName = "complete")]
public bool Complete { get; set; }
// *** Enable Optimistic Concurrency *** //
[JsonProperty(PropertyName = "version")]
public string Version { set; get; }
}
Anwendungen, die nicht typisierte Tabellen verwenden, ermöglichen eine optimistische Parallelität, indem Sie das Version
Flag für die SystemProperties
der Tabelle wie folgt festlegen.
//Enable optimistic concurrency by retrieving version
todoTable.SystemProperties |= MobileServiceSystemProperties.Version;
Zusätzlich zur Aktivierung optimistischer Parallelität müssen Sie beim Aufrufen UpdateAsync-auch die MobileServicePreconditionFailedException<T>
Ausnahme in Ihrem Code abfangen. Lösen Sie den Konflikt, indem Sie den richtigen version
auf den aktualisierten Datensatz anwenden und UpdateAsync- mit dem aufgelösten Datensatz aufrufen. Der folgende Code zeigt, wie ein Schreibkonflikt gelöst wird, nachdem ein Fehler erkannt wurde:
private async void UpdateToDoItem(TodoItem item)
{
MobileServicePreconditionFailedException<TodoItem> exception = null;
try
{
//update at the remote table
await todoTable.UpdateAsync(item);
}
catch (MobileServicePreconditionFailedException<TodoItem> writeException)
{
exception = writeException;
}
if (exception != null)
{
// Conflict detected, the item has changed since the last query
// Resolve the conflict between the local and server item
await ResolveConflict(item, exception.Item);
}
}
private async Task ResolveConflict(TodoItem localItem, TodoItem serverItem)
{
//Ask user to choose the resolution between versions
MessageDialog msgDialog = new MessageDialog(
String.Format("Server Text: \"{0}\" \nLocal Text: \"{1}\"\n",
serverItem.Text, localItem.Text),
"CONFLICT DETECTED - Select a resolution:");
UICommand localBtn = new UICommand("Commit Local Text");
UICommand ServerBtn = new UICommand("Leave Server Text");
msgDialog.Commands.Add(localBtn);
msgDialog.Commands.Add(ServerBtn);
localBtn.Invoked = async (IUICommand command) =>
{
// To resolve the conflict, update the version of the item being committed. Otherwise, you will keep
// catching a MobileServicePreConditionFailedException.
localItem.Version = serverItem.Version;
// Updating recursively here just in case another change happened while the user was making a decision
UpdateToDoItem(localItem);
};
ServerBtn.Invoked = async (IUICommand command) =>
{
RefreshTodoItems();
};
await msgDialog.ShowAsync();
}
Weitere Informationen finden Sie im Thema Offlinedatensynchronisierung in Azure Mobile Apps Thema.
Binden von Daten an eine Windows-Benutzeroberfläche
In diesem Abschnitt wird gezeigt, wie zurückgegebene Datenobjekte mithilfe von UI-Elementen in einer Windows-App angezeigt werden. Der folgende Beispielcode bindet an die Quelle der Liste mit einer Abfrage nach unvollständigen Elementen. Die MobileServiceCollection- erstellt eine Bindungsauflistung für mobile Apps.
// This query filters out completed TodoItems.
MobileServiceCollection<TodoItem, TodoItem> items = await todoTable
.Where(todoItem => todoItem.Complete == false)
.ToCollectionAsync();
// itemsControl is an IEnumerable that could be bound to a UI list control
IEnumerable itemsControl = items;
// Bind this to a ListBox
ListBox lb = new ListBox();
lb.ItemsSource = items;
Einige Steuerelemente in der verwalteten Laufzeit unterstützen eine Schnittstelle namens ISupportIncrementalLoading. Diese Schnittstelle ermöglicht es Steuerelementen, zusätzliche Daten anzufordern, wenn der Benutzer scrollt. Es gibt integrierte Unterstützung für diese Schnittstelle für universelle Windows-Apps über MobileServiceIncrementalLoadingCollection-, die die Aufrufe von den Steuerelementen automatisch verarbeitet. Verwenden Sie MobileServiceIncrementalLoadingCollection
in Windows-Apps wie folgt:
MobileServiceIncrementalLoadingCollection<TodoItem,TodoItem> items;
items = todoTable.Where(todoItem => todoItem.Complete == false).ToIncrementalLoadingCollection();
ListBox lb = new ListBox();
lb.ItemsSource = items;
Um die neue Auflistung für Windows Phone 8- und "Silverlight"-Apps zu verwenden, verwenden Sie die ToCollection
Erweiterungsmethoden für IMobileServiceTableQuery<T>
und IMobileServiceTable<T>
. Rufen Sie zum Laden von Daten LoadMoreItemsAsync()
auf.
MobileServiceCollection<TodoItem, TodoItem> items = todoTable.Where(todoItem => todoItem.Complete==false).ToCollection();
await items.LoadMoreItemsAsync();
Wenn Sie die durch Aufrufen von ToCollectionAsync
oder ToCollection
erstellte Auflistung verwenden, erhalten Sie eine Sammlung, die an UI-Steuerelemente gebunden werden kann. Diese Sammlung ist seitenaufwendigend. Da die Sammlung Daten aus dem Netzwerk lädt, schlägt das Laden manchmal fehl. Um solche Fehler zu behandeln, überschreiben Sie die OnException
-Methode für MobileServiceIncrementalLoadingCollection
, um Ausnahmen zu behandeln, die sich aus Aufrufen von LoadMoreItemsAsync
ergeben.
Überlegen Sie, ob Ihre Tabelle viele Felder enthält, aber nur einige felder in Ihrem Steuerelement anzeigen möchten. Sie können die Anleitungen im vorherigen Abschnitt "Auswählen bestimmter Spalten" verwenden, um bestimmte Spalten auszuwählen, die in der Benutzeroberfläche angezeigt werden sollen.
Ändern des Seitenformats
Azure Mobile Apps gibt standardmäßig maximal 50 Elemente pro Anforderung zurück. Sie können die Seitengröße ändern, indem Sie die maximale Seitengröße sowohl auf dem Client als auch auf dem Server erhöhen. Um die angeforderte Seitengröße zu erhöhen, geben Sie PullOptions
an, wenn Sie PullAsync()
verwenden:
PullOptions pullOptions = new PullOptions
{
MaxPageSize = 100
};
Angenommen, Sie haben die PageSize
gleich oder größer als 100 innerhalb des Servers gemacht, eine Anforderung gibt bis zu 100 Elemente zurück.
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. Um eine Offlinetabelle zu erstellen, bereiten Sie zuerst Ihr Projekt vor.
- Klicken Sie in Visual Studio mit der rechten Maustaste auf die Projektmappe >NuGet-Pakete für Projektmappe verwalten..., und installieren Sie dann das Microsoft.Azure.Mobile.Client.SQLiteStore NuGet-Paket für alle Projekte in der Projektmappe.
- Drücken Sie für Windows-Geräte Verweise>Verweis hinzufügen..., erweitern Sie den Ordner Windows Ordner >Erweiterungen, und aktivieren Sie dann das entsprechende SQLite für Windows SDK zusammen mit dem Visual C++ 2013 Runtime for Windows SDK. Die SQLite SDK-Namen variieren geringfügig bei jeder Windows-Plattform.
Bevor ein Tabellenverweis erstellt werden kann, muss der lokale Speicher vorbereitet werden:
var store = new MobileServiceSQLiteStore(Constants.OfflineDbPath);
store.DefineTable<TodoItem>();
//Initializes the SyncContext using the default IMobileServiceSyncHandler.
await this.client.SyncContext.InitializeAsync(store);
Die Store-Initialisierung erfolgt normalerweise unmittelbar nach dem Erstellen des Clients. Die OfflineDbPath- sollte ein Dateiname sein, der für die Verwendung auf allen von Ihnen unterstützten Plattformen geeignet ist. Wenn der Pfad ein vollqualifizierter Pfad ist (d. h. er beginnt mit einem Schrägstrich), wird dieser Pfad verwendet. Wenn der Pfad nicht vollständig qualifiziert ist, wird die Datei an einem plattformspezifischen Speicherort platziert.
- Für iOS- und Android-Geräte ist der Standardpfad der Ordner "Persönliche Dateien".
- Für Windows-Geräte ist der Standardpfad der anwendungsspezifische Ordner "AppData".
Mithilfe der GetSyncTable<>
-Methode kann ein Tabellenverweis abgerufen werden:
var table = client.GetSyncTable<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. Hier ist eine typische Synchronisierungsmethode:
public async Task SyncAsync()
{
ReadOnlyCollection<MobileServiceTableOperationError> syncErrors = null;
try
{
await this.client.SyncContext.PushAsync();
await this.todoTable.PullAsync(
//The first parameter is a query name that is used internally by the client SDK to implement incremental sync.
//Use a different query name for each unique query in your program
"allTodoItems",
this.todoTable.CreateQuery());
}
catch (MobileServicePushFailedException exc)
{
if (exc.PushResult != null)
{
syncErrors = exc.PushResult.Errors;
}
}
// Simple error/conflict handling. A real application would handle the various errors like network conditions,
// server conflicts and others via the IMobileServiceSyncHandler.
if (syncErrors != null)
{
foreach (var error in syncErrors)
{
if (error.OperationKind == MobileServiceTableOperationKind.Update && error.Result != null)
{
//Update failed, reverting to server's copy.
await error.CancelAndUpdateItemAsync(error.Result);
}
else
{
// Discard local change.
await error.CancelAndDiscardItemAsync();
}
Debug.WriteLine(@"Error executing sync operation. Item: {0} ({1}). Operation discarded.", error.TableName, error.Item["id"]);
}
}
}
Wenn das erste Argument für PullAsync
null ist, wird die inkrementelle Synchronisierung nicht verwendet. Jeder Synchronisierungsvorgang ruft alle Datensätze ab.
Das SDK führt vor dem Abrufen von Datensätzen eine implizite PushAsync()
aus.
Die Konfliktbehandlung erfolgt in einer PullAsync()
-Methode. Sie können Konflikte auf die gleiche Weise wie Onlinetabellen behandeln. Der Konflikt wird erzeugt, wenn PullAsync()
anstelle des Einfügens, Aktualisierens oder Löschens aufgerufen wird. Wenn mehrere Konflikte auftreten, werden sie in einer einzigen MobileServicePushFailedException gebündelt. Behandeln Sie jeden Fehler separat.
Arbeiten mit einer benutzerdefinierten API
Mit einer benutzerdefinierten API können Sie benutzerdefinierte Endpunkte definieren, die Serverfunktionen verfügbar machen, die keinem Einfüge-, Aktualisierungs-, Lösch- oder Lesevorgang zugeordnet sind. Mithilfe einer benutzerdefinierten API können Sie mehr Kontrolle über Messaging haben, z. B. das Lesen und Festlegen von HTTP-Nachrichtenkopfzeilen und das Definieren eines anderen Nachrichtentextformats als JSON.
Sie rufen eine benutzerdefinierte API auf, indem Sie eine der InvokeApiAsync- Methoden auf dem Client aufrufen. Beispielsweise sendet die folgende Codezeile eine POST-Anforderung an die completeAll API im Back-End:
var result = await client.InvokeApiAsync<MarkAllResult>("completeAll", System.Net.Http.HttpMethod.Post, null);
Dieses Formular ist ein typierter Methodenaufruf und erfordert, dass der MarkAllResult- Rückgabetyp definiert ist. Sowohl typisierte als auch nicht typisierte Methoden werden unterstützt.
Die InvokeApiAsync()-Methode stellt "/api/" der API voran, die Sie aufrufen möchten, es sei denn, die API beginnt mit einem "/". Zum Beispiel:
-
InvokeApiAsync("completeAll",...)
aufruft /api/completeAll im Back-End -
InvokeApiAsync("/.auth/me",...)
aufruft /.auth/me im Back-End
Sie können InvokeApiAsync verwenden, um eine beliebige WebAPI aufzurufen, einschließlich der WebAPIs, die nicht mit Azure Mobile Apps definiert sind. Wenn Sie InvokeApiAsync() verwenden, werden die entsprechenden Header, einschließlich Authentifizierungsheadern, mit der Anforderung gesendet.
Authentifizieren von Benutzern
Mobile Apps unterstützt die Authentifizierung und Autorisierung von App-Benutzern mithilfe verschiedener externer Identitätsanbieter: Facebook, Google, Microsoft-Konto, Twitter und Microsoft Entra-ID. Sie können Berechtigungen für Tabellen festlegen, um den Zugriff für bestimmte Vorgänge auf authentifizierte Benutzer einzuschränken. Sie können auch die Identität authentifizierter Benutzer verwenden, um Autorisierungsregeln in Serverskripts zu implementieren.
Zwei Authentifizierungsflüsse werden unterstützt: vom Client verwalteten und vom Server verwalteten-Fluss. Der vom Server verwaltete Fluss bietet die einfachste Authentifizierung, da er auf der Webauthentifizierungsschnittstelle des Anbieters basiert. Der vom Client verwaltete Fluss ermöglicht eine tiefere Integration in gerätespezifische Funktionen, da er auf anbieterspezifischen gerätespezifischen SDKs basiert.
Anmerkung
Wir empfehlen die Verwendung eines clientverwalteten Flusses in Ihren Produktions-Apps.
Zum Einrichten der Authentifizierung müssen Sie Ihre App bei einem oder mehreren Identitätsanbietern registrieren. Der Identitätsanbieter generiert eine Client-ID und einen geheimen Clientschlüssel für Ihre App. Diese Werte werden dann in Ihrem Back-End festgelegt, um die Azure App Service-Authentifizierung/Autorisierung zu aktivieren.
Die folgenden Themen werden in diesem Abschnitt behandelt:
- vom Client verwalteten Authentifizierung
- vom Server verwalteten Authentifizierung
- Zwischenspeichern des Authentifizierungstokens
Clientverwaltete Authentifizierung
Ihre App kann sich unabhängig an den Identitätsanbieter wenden und dann das zurückgegebene Token während der Anmeldung mit Ihrem Back-End bereitstellen. Mit diesem Clientfluss können Sie benutzern eine Einmalige Anmeldung ermöglichen oder zusätzliche Benutzerdaten vom Identitätsanbieter abrufen. Die Clientflussauthentifizierung wird bevorzugt, um einen Serverfluss zu verwenden, da das Identitätsanbieter-SDK ein nativeres UX-Verhalten bietet und mehr Anpassungen ermöglicht.
Beispiele für die folgenden Clientflussauthentifizierungsmuster:
Authentifizieren von Benutzern mit der Active Directory-Authentifizierungsbibliothek
Sie können die Active Directory-Authentifizierungsbibliothek (Active Directory Authentication Library, ADAL) verwenden, um die Benutzerauthentifizierung über den Client mithilfe der Microsoft Entra-Authentifizierung zu initiieren.
Warnung
Die Unterstützung für die Active Directory-Authentifizierungsbibliothek (ADAL) endet im Dezember 2022. Apps, die ADAL auf vorhandenen Betriebssystemversionen verwenden, funktionieren weiterhin, aber der technische Support und Sicherheitsupdates werden beendet. Weitere Informationen finden Sie unter Migrieren von Apps zu MSAL.
Konfigurieren Sie Ihr mobiles App-Back-End für die Microsoft Entra-Anmeldung, indem Sie den Anleitung zum Konfigurieren von App Service für Active Directory-Anmeldung Lernprogramm ausführen. Stellen Sie sicher, dass Sie den optionalen Schritt zum Registrieren einer systemeigenen Clientanwendung ausführen.
Öffnen Sie in Visual Studio Ihr Projekt, und fügen Sie einen Verweis auf das
Microsoft.IdentityModel.Clients.ActiveDirectory
NuGet-Paket hinzu. Schließen Sie bei der Suche Vorabversionen ein.Fügen Sie Ihrer Anwendung entsprechend der verwendeten Plattform den folgenden Code hinzu. Nehmen Sie in jedem die folgenden Ersetzungen vor:
Ersetzen Sie INSERT-AUTHORITY-HERE- durch den Namen des Mandanten, in dem Sie Ihre Anwendung bereitgestellt haben. Das Format sollte
https://login.microsoftonline.com/contoso.onmicrosoft.com
sein. Dieser Wert kann auf der Registerkarte "Domäne" in Ihrer Microsoft Entra-ID im [Azure-Portal] kopiert werden.Ersetzen Sie INSERT-RESOURCE-ID-HERE- durch die Client-ID für Ihr mobiles App-Back-End. Sie können die Client-ID über die Registerkarte Erweiterten unter Microsoft Entra-Einstellungen im Portal abrufen.
Ersetzen Sie INSERT-CLIENT-ID-HERE- durch die Client-ID, die Sie aus der systemeigenen Clientanwendung kopiert haben.
Ersetzen Sie INSERT-REDIRECT-URI-HERE durch den
/.auth/login/done
Endpunkt Ihrer Website, indem Sie das HTTPS-Schema verwenden. Dieser Wert sollte mithttps://contoso.azurewebsites.net/.auth/login/done
vergleichbar sein.Der für jede Plattform benötigte Code folgt:
Windows:
private MobileServiceUser user; private async Task AuthenticateAsync() { string authority = "INSERT-AUTHORITY-HERE"; string resourceId = "INSERT-RESOURCE-ID-HERE"; string clientId = "INSERT-CLIENT-ID-HERE"; string redirectUri = "INSERT-REDIRECT-URI-HERE"; while (user == null) { string message; try { AuthenticationContext ac = new AuthenticationContext(authority); AuthenticationResult ar = await ac.AcquireTokenAsync(resourceId, clientId, new Uri(redirectUri), new PlatformParameters(PromptBehavior.Auto, false) ); JObject payload = new JObject(); payload["access_token"] = ar.AccessToken; user = await App.MobileService.LoginAsync( MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload); message = string.Format("You are now logged in - {0}", user.UserId); } catch (InvalidOperationException) { message = "You must log in. Login Required"; } var dialog = new MessageDialog(message); dialog.Commands.Add(new UICommand("OK")); await dialog.ShowAsync(); } }
Xamarin.iOS-
private MobileServiceUser user; private async Task AuthenticateAsync(UIViewController view) { string authority = "INSERT-AUTHORITY-HERE"; string resourceId = "INSERT-RESOURCE-ID-HERE"; string clientId = "INSERT-CLIENT-ID-HERE"; string redirectUri = "INSERT-REDIRECT-URI-HERE"; try { AuthenticationContext ac = new AuthenticationContext(authority); AuthenticationResult ar = await ac.AcquireTokenAsync(resourceId, clientId, new Uri(redirectUri), new PlatformParameters(view)); JObject payload = new JObject(); payload["access_token"] = ar.AccessToken; user = await client.LoginAsync( MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload); } catch (Exception ex) { Console.Error.WriteLine(@"ERROR - AUTHENTICATION FAILED {0}", ex.Message); } }
Xamarin.Android
private MobileServiceUser user; private async Task AuthenticateAsync() { string authority = "INSERT-AUTHORITY-HERE"; string resourceId = "INSERT-RESOURCE-ID-HERE"; string clientId = "INSERT-CLIENT-ID-HERE"; string redirectUri = "INSERT-REDIRECT-URI-HERE"; try { AuthenticationContext ac = new AuthenticationContext(authority); AuthenticationResult ar = await ac.AcquireTokenAsync(resourceId, clientId, new Uri(redirectUri), new PlatformParameters(this)); JObject payload = new JObject(); payload["access_token"] = ar.AccessToken; user = await client.LoginAsync( MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload); } catch (Exception ex) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.SetMessage(ex.Message); builder.SetTitle("You must log in. Login Required"); builder.Create().Show(); } } protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) { base.OnActivityResult(requestCode, resultCode, data); AuthenticationAgentContinuationHelper.SetAuthenticationAgentContinuationEventArgs(requestCode, resultCode, data); }
Einmaliges Anmelden mit einem Token von Facebook oder Google
Sie können den Clientfluss wie in diesem Codeausschnitt für Facebook oder Google gezeigt verwenden.
var token = new JObject();
// Replace access_token_value with actual value of your access token obtained
// using the Facebook or Google SDK.
token.Add("access_token", "access_token_value");
private MobileServiceUser user;
private async Task AuthenticateAsync()
{
while (user == null)
{
string message;
try
{
// Change MobileServiceAuthenticationProvider.Facebook
// to MobileServiceAuthenticationProvider.Google if using Google auth.
user = await client.LoginAsync(MobileServiceAuthenticationProvider.Facebook, token);
message = string.Format("You are now logged in - {0}", user.UserId);
}
catch (InvalidOperationException)
{
message = "You must log in. Login Required";
}
var dialog = new MessageDialog(message);
dialog.Commands.Add(new UICommand("OK"));
await dialog.ShowAsync();
}
}
Serververwaltete Authentifizierung
Nachdem Sie Ihren Identitätsanbieter registriert haben, rufen Sie die LoginAsync--Methode auf dem MobileServiceClient mit dem MobileServiceAuthenticationProvider Wert Ihres Anbieters auf. Der folgende Code initiiert beispielsweise eine Serverflussanmeldung mithilfe von Facebook.
private MobileServiceUser user;
private async System.Threading.Tasks.Task Authenticate()
{
while (user == null)
{
string message;
try
{
user = await client
.LoginAsync(MobileServiceAuthenticationProvider.Facebook);
message =
string.Format("You are now logged in - {0}", user.UserId);
}
catch (InvalidOperationException)
{
message = "You must log in. Login Required";
}
var dialog = new MessageDialog(message);
dialog.Commands.Add(new UICommand("OK"));
await dialog.ShowAsync();
}
}
Wenn Sie einen anderen Identitätsanbieter als Facebook verwenden, ändern Sie den Wert von MobileServiceAuthenticationProvider in den Wert für Ihren Anbieter.
In einem Serverablauf verwaltet Azure App Service den OAuth-Authentifizierungsfluss, indem die Anmeldeseite des ausgewählten Anbieters angezeigt wird. Sobald der Identitätsanbieter zurückgegeben wird, generiert Azure App Service ein App Service-Authentifizierungstoken. Die LoginAsync--Methode gibt eine MobileServiceUser-zurück, die sowohl die UserId des authentifizierten Benutzers als auch das MobileServiceAuthenticationToken als JSON-Webtoken (JWT) bereitstellt. Dieses Token kann zwischengespeichert und wiederverwendet werden, bis es abläuft. Weitere Informationen finden Sie unter Zwischenspeichern des Authentifizierungstokens.
Anmerkung
Unter den Deckeln verwendet Azure Mobile Apps eine Xamarin.Essentials WebAuthenticator, um die Arbeit zu erledigen. Sie müssen die Antwort des Diensts behandeln, indem Sie wieder in Xamarin.Essentials aufrufen. Ausführliche Informationen finden Sie unter WebAuthenticator.
Zwischenspeichern des Authentifizierungstokens
In einigen Fällen kann der Aufruf der Anmeldemethode nach der ersten erfolgreichen Authentifizierung vermieden werden, indem das Authentifizierungstoken vom Anbieter gespeichert wird. Microsoft Store- und UWP-Apps können PasswordVault- verwenden, um das aktuelle Authentifizierungstoken nach einer erfolgreichen Anmeldung wie folgt zwischenzuspeichern:
await client.LoginAsync(MobileServiceAuthenticationProvider.Facebook);
PasswordVault vault = new PasswordVault();
vault.Add(new PasswordCredential("Facebook", client.currentUser.UserId,
client.currentUser.MobileServiceAuthenticationToken));
Der UserId-Wert wird als Benutzername der Anmeldeinformationen gespeichert, und das Token ist das als Kennwort gespeicherte Wert. Bei nachfolgenden Start-ups können Sie die PasswordVault- auf zwischengespeicherte Anmeldeinformationen überprüfen. Im folgenden Beispiel werden zwischengespeicherte Anmeldeinformationen verwendet, wenn sie gefunden werden, und andernfalls wird versucht, sich erneut beim Back-End zu authentifizieren:
// Try to retrieve stored credentials.
var creds = vault.FindAllByResource("Facebook").FirstOrDefault();
if (creds != null)
{
// Create the current user from the stored credentials.
client.currentUser = new MobileServiceUser(creds.UserName);
client.currentUser.MobileServiceAuthenticationToken =
vault.Retrieve("Facebook", creds.UserName).Password;
}
else
{
// Regular login flow and cache the token as shown above.
}
Wenn Sie sich bei einem Benutzer abmelden, müssen Sie auch die gespeicherten Anmeldeinformationen wie folgt entfernen:
client.Logout();
vault.Remove(vault.Retrieve("Facebook", client.currentUser.UserId));
Wenn Sie die clientverwaltete Authentifizierung verwenden, können Sie auch das von Ihrem Anbieter abgerufene Zugriffstoken zwischenspeichern, z. B. Facebook oder Twitter. Dieses Token kann bereitgestellt werden, um ein neues Authentifizierungstoken aus dem Back-End anzufordern:
var token = new JObject();
// Replace <your_access_token_value> with actual value of your access token
token.Add("access_token", "<your_access_token_value>");
// Authenticate using the access token.
await client.LoginAsync(MobileServiceAuthenticationProvider.Facebook, token);
Verschiedene Themen
Behandeln von Fehlern
Wenn im Back-End ein Fehler auftritt, löst das Client-SDK eine MobileServiceInvalidOperationException
aus. Das folgende Beispiel zeigt, wie eine Ausnahme behandelt wird, die vom Back-End zurückgegeben wird:
private async void InsertTodoItem(TodoItem todoItem)
{
// This code inserts a new TodoItem into the database. When the operation completes
// and App Service has assigned an ID, the item is added to the CollectionView
try
{
await todoTable.InsertAsync(todoItem);
items.Add(todoItem);
}
catch (MobileServiceInvalidOperationException e)
{
// Handle error
}
}
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 sogar Antwortstatuscodes ändern. Sie können wie im folgenden Beispiel einen benutzerdefinierten DelegatingHandlerverwenden:
public async Task CallClientWithHandler()
{
MobileServiceClient client = new MobileServiceClient("AppUrl", new MyHandler());
IMobileServiceTable<TodoItem> todoTable = client.GetTable<TodoItem>();
var newItem = new TodoItem { Text = "Hello world", Complete = false };
await todoTable.InsertAsync(newItem);
}
public class MyHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage>
SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// Change the request-side here based on the HttpRequestMessage
request.Headers.Add("x-my-header", "my value");
// Do the request
var response = await base.SendAsync(request, cancellationToken);
// Change the response-side here based on the HttpResponseMessage
// Return the modified response
return response;
}
}
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;
}
}