De Azure Mobile Apps-clientbibliotheek voor .NET gebruiken
Notitie
Dit product is buiten gebruik gesteld. Zie de Community Toolkit Datasync-bibliotheekvoor een vervanging voor projecten met .NET 8 of hoger.
Deze handleiding laat zien hoe u algemene scenario's uitvoert met behulp van de .NET-clientbibliotheek voor Azure Mobile Apps. Gebruik de .NET-clientbibliotheek in elke .NET 6- of .NET Standard 2.0-toepassing, waaronder MAUI, Xamarin en Windows (WPF, UWP en WinUI).
Als u nog niet eerder met Azure Mobile Apps werkt, kunt u overwegen eerst een van de snelstartzelfstudies uit te voeren:
- (ANDROID en iOS)
- Uno Platform-
- Windows -
- Windows (WinUI3)
- Windows -
- Xamarin (Android Native)
- Xamarin (iOS Native)
- Xamarin Forms (Android en iOS)
Notitie
In dit artikel wordt de nieuwste versie (v6.0) van het Microsoft Datasync Framework behandeld. Zie de documentatie v4.2.0voor oudere clients.
Ondersteunde platforms
De .NET-clientbibliotheek ondersteunt elk .NET Standard 2.0- of .NET 6-platform, waaronder:
- .NET MAUI voor Android-, iOS- en Windows-platforms.
- Android-API-niveau 21 en hoger (Xamarin en Android voor .NET).
- iOS-versie 12.0 en hoger (Xamarin en iOS voor .NET).
- Universal Windows Platform bouwt 19041 en hoger.
- Windows Presentation Framework (WPF).
- Windows App SDK (WinUI 3).
- Xamarin.Forms
Daarnaast zijn er monsters gemaakt voor en Uno Platform. Het TodoApp-voorbeeld bevat een voorbeeld van elk getest platform.
Installatie en vereisten
Voeg de volgende bibliotheken toe vanuit NuGet:
- Microsoft.Datasync.Client-
- Microsoft.Datasync.Client.SQLiteStore als u offlinetabellen gebruikt.
Als u een platformproject gebruikt (bijvoorbeeld .NET MAUI), moet u ervoor zorgen dat u de bibliotheken toevoegt aan het platformproject en een gedeeld project.
De serviceclient maken
Met de volgende code wordt de serviceclient gemaakt, die wordt gebruikt om alle communicatie met de back-end- en offlinetabellen te coördineren.
var options = new DatasyncClientOptions
{
// Options set here
};
var client = new DatasyncClient("MOBILE_APP_URL", options);
Vervang in de voorgaande code MOBILE_APP_URL
door de URL van de ASP.NET Core-back-end. De client moet worden gemaakt als een singleton. Als u een verificatieprovider gebruikt, kan deze als volgt worden geconfigureerd:
var options = new DatasyncClientOptions
{
// Options set here
};
var client = new DatasyncClient("MOBILE_APP_URL", authProvider, options);
Verderop in dit document vindt u meer informatie over de verificatieprovider.
Opties
U kunt als volgt een volledige (standaard) set opties maken:
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
Normaal gesproken wordt een HTTP-aanvraag gedaan door de aanvraag door te geven via de verificatieprovider (waarmee de Authorization
-header voor de momenteel geverifieerde gebruiker wordt toegevoegd) voordat de aanvraag wordt verzonden. U kunt desgewenst meer delegeringshandlers toevoegen. Elke aanvraag doorloopt de delegerende handlers voordat deze naar de service worden verzonden. Door handlers te delegeren, kunt u extra headers toevoegen, nieuwe pogingen uitvoeren of logboekregistratiemogelijkheden bieden.
Voorbeelden van het delegeren van handlers zijn beschikbaar voor logboekregistratie en het toevoegen van aanvraagheaders verderop in dit artikel.
IdGenerator
Wanneer een entiteit wordt toegevoegd aan een offlinetabel, moet deze een id hebben. Er wordt een id gegenereerd als er geen id is opgegeven. Met de optie IdGenerator
kunt u de id aanpassen die wordt gegenereerd. Standaard wordt er een wereldwijd unieke id gegenereerd. Met de volgende instelling wordt bijvoorbeeld een tekenreeks gegenereerd die de tabelnaam en een GUID bevat:
var options = new DatasyncClientOptions
{
IdGenerator = (table) => $"{table}-{Guid.NewGuid().ToString("D").ToUpperInvariant()}"
}
InstallationId
Als een InstallationId
is ingesteld, wordt bij elke aanvraag een aangepaste header X-ZUMO-INSTALLATION-ID
verzonden om de combinatie van de toepassing op een specifiek apparaat te identificeren. Deze header kan worden vastgelegd in logboeken en stelt u in staat om het aantal afzonderlijke installaties voor uw app te bepalen. Als u InstallationId
gebruikt, moet de id worden opgeslagen in permanente opslag op het apparaat, zodat unieke installaties kunnen worden bijgehouden.
OfflineStore
De OfflineStore
wordt gebruikt bij het configureren van offlinegegevenstoegang. Zie Werken met offlinetabellenvoor meer informatie.
ParallelOperations
Een deel van het offlinesynchronisatieproces omvat het pushen van in de wachtrij geplaatste bewerkingen naar de externe server. Wanneer de pushbewerking wordt geactiveerd, worden de bewerkingen verzonden in de volgorde waarin ze zijn ontvangen. U kunt desgewenst maximaal acht threads gebruiken om deze bewerkingen te pushen. Parallelle bewerkingen gebruiken meer resources op zowel client als server om de bewerking sneller te voltooien. De volgorde waarin bewerkingen op de server binnenkomen, kan niet worden gegarandeerd wanneer u meerdere threads gebruikt.
SerializerSettings
Als u de serialisatie-instellingen op de gegevenssynchronisatieserver hebt gewijzigd, moet u dezelfde wijzigingen aanbrengen in de SerializerSettings
op de client. Met deze optie kunt u uw eigen serialisatie-instellingen opgeven.
TableEndpointResolver
Volgens de conventie bevinden tabellen zich op de externe service op het /tables/{tableName}
pad (zoals opgegeven door het kenmerk Route
in de servercode). Tabellen kunnen echter bestaan op elk eindpuntpad. Het TableEndpointResolver
is een functie waarmee een tabelnaam wordt omgezet in een pad voor communicatie met de externe service.
Met de volgende wijzigingen wordt bijvoorbeeld de aanname gewijzigd, zodat alle tabellen zich onder /api
bevinden:
var options = new DatasyncClientOptions
{
TableEndpointResolver = (table) => $"/api/{table}"
};
UserAgent
De gegevenssynchronisatieclient genereert een geschikte User-Agent headerwaarde op basis van de versie van de bibliotheek. Sommige ontwikkelaars vinden dat de header van de gebruikersagent informatie over de client lekt. U kunt de eigenschap UserAgent
instellen op een geldige headerwaarde.
Werken met externe tabellen
De volgende sectie bevat informatie over het zoeken en ophalen van records en het wijzigen van de gegevens in een externe tabel. De volgende onderwerpen worden behandeld:
- Een tabelreferentie maken
- gegevens opvragen
- Items uit een query tellen
- externe gegevens opzoeken op id
- Gegevens invoegen op de externe server
- Gegevens bijwerken op de externe server
- Gegevens verwijderen op de externe server
- Conflictoplossing en optimistische gelijktijdigheid
Een externe tabelreferentie maken
Als u een externe tabelreferentie wilt maken, gebruikt u GetRemoteTable<T>
:
IRemoteTable<TodoItem> remoteTable = client.GetRemoteTable();
Als u een tabel met het kenmerk Alleen-lezen wilt retourneren, gebruikt u de IReadOnlyRemoteTable<T>
versie:
IReadOnlyRemoteTable<TodoItem> remoteTable = client.GetRemoteTable();
Het modeltype moet het ITableData
contract van de service implementeren. Gebruik DatasyncClientData
om de vereiste velden op te geven:
public class TodoItem : DatasyncClientData
{
public string Title { get; set; }
public bool IsComplete { get; set; }
}
Het DatasyncClientData
-object bevat:
-
Id
(tekenreeks): een globaal unieke id voor het item. -
UpdatedAt
(System.DataTimeOffset): de datum/tijd waarop het item voor het laatst is bijgewerkt. -
Version
(tekenreeks): een ondoorzichtige tekenreeks die wordt gebruikt voor versiebeheer. -
Deleted
(Booleaanse waarde): alstrue
, wordt het item verwijderd.
De service onderhoudt deze velden. Pas deze velden niet aan als onderdeel van uw clienttoepassing.
Modellen kunnen worden geannoteerd met behulp van Newtonsoft.JSON kenmerken. De naam van de tabel kan worden opgegeven met behulp van het kenmerk DataTable
:
[DataTable("todoitem")]
public class MyTodoItemClass : DatasyncClientData
{
public string Title { get; set; }
public bool IsComplete { get; set; }
}
U kunt ook de naam van de tabel opgeven in de GetRemoteTable()
-aanroep:
IRemoteTable<TodoItem> remoteTable = client.GetRemoteTable("todoitem");
De client gebruikt het pad /tables/{tablename}
als de URI. De tabelnaam is ook de naam van de offlinetabel in de SQLite-database.
Ondersteunde typen
Afgezien van primitieve typen (int, float, tekenreeks, enzovoort), worden de volgende typen ondersteund voor modellen:
-
System.DateTime
- als een ISO-8601 UTC-datum-/tijdtekenreeks met ms-nauwkeurigheid. -
System.DateTimeOffset
- als een ISO-8601 UTC-datum-/tijdtekenreeks met ms-nauwkeurigheid. -
System.Guid
- opgemaakt als 32 cijfers gescheiden als afbreekstreepjes.
Query's uitvoeren op gegevens van een externe server
De externe tabel kan worden gebruikt met LINQ-achtige instructies, waaronder:
- Filteren met een
.Where()
component. - Sorteren met verschillende
.OrderBy()
componenten. - Eigenschappen selecteren met
.Select()
. - Paging met
.Skip()
en.Take()
.
Items uit een query tellen
Als u een telling nodig hebt van de items die door de query worden geretourneerd, kunt u .CountItemsAsync()
gebruiken voor een tabel of .LongCountAsync()
voor een query:
// 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();
Deze methode veroorzaakt een retour naar de server. U kunt ook een telling krijgen tijdens het invullen van een lijst (bijvoorbeeld), om de extra retour te vermijden:
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);
}
Het aantal wordt ingevuld na de eerste aanvraag om de inhoud van de tabel op te halen.
Alle gegevens retourneren
Gegevens worden geretourneerd via een IAsyncEnumerable:
var enumerable = remoteTable.ToAsyncEnumerable();
await foreach (var item in enumerable)
{
// Process each item
}
Gebruik een van de volgende afsluitclausules om de IAsyncEnumerable<T>
te converteren naar een andere verzameling:
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();
Achter de schermen verwerkt de externe tabel het paggineren van het resultaat voor u. Alle items worden geretourneerd, ongeacht hoeveel aanvragen aan de serverzijde nodig zijn om aan de query te voldoen. Deze elementen zijn ook beschikbaar voor queryresultaten (bijvoorbeeld remoteTable.Where(m => m.Rating == "R")
).
Het Data Sync Framework biedt ook ConcurrentObservableCollection<T>
: een thread-veilige waarneembare verzameling. Deze klasse kan worden gebruikt in de context van UI-toepassingen die normaal gesproken ObservableCollection<T>
gebruiken voor het beheren van een lijst (bijvoorbeeld Xamarin Forms of FORMS-lijsten). U kunt een ConcurrentObservableCollection<T>
rechtstreeks vanuit een tabel of query wissen en laden:
var collection = new ConcurrentObservableCollection<T>();
await remoteTable.ToObservableCollection(collection);
Als u .ToObservableCollection(collection)
gebruikt, wordt de CollectionChanged
gebeurtenis één keer geactiveerd voor de hele verzameling in plaats van voor afzonderlijke items, wat resulteert in een snellere hertekeningstijd.
De ConcurrentObservableCollection<T>
heeft ook predicaatgestuurde wijzigingen:
// 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);
Predicaatgestuurde wijzigingen kunnen worden gebruikt in gebeurtenis-handlers wanneer de index van het item niet van tevoren bekend is.
Gegevens filteren
U kunt een .Where()
component gebruiken om gegevens te filteren. Bijvoorbeeld:
var items = await remoteTable.Where(x => !x.IsComplete).ToListAsync();
Filteren wordt uitgevoerd op de service vóór de IAsyncEnumerable en op de client na de IAsyncEnumerable. Bijvoorbeeld:
var items = (await remoteTable.Where(x => !x.IsComplete).ToListAsync()).Where(x => x.Title.StartsWith("The"));
De eerste .Where()
component (alleen onvolledige items retourneren) wordt uitgevoerd op de service, terwijl de tweede .Where()
component (beginnend met 'De') wordt uitgevoerd op de client.
De Where
-component ondersteunt bewerkingen die worden omgezet in de OData-subset. Bewerkingen zijn onder andere:
- Relationele operatoren (
==
,!=
,<
,<=
,>
,>=
), - Rekenkundige operatoren (
+
,-
,/
,*
,%
), - Getalprecisie (
Math.Floor
,Math.Ceiling
), - Tekenreeksfuncties (
Length
,Substring
,Replace
,IndexOf
,Equals
,StartsWith
,EndsWith
) (alleen ordinale en invariante culturen), - Datumeigenschappen (
Year
,Month
,Day
,Hour
,Minute
,Second
), - Toegangseigenschappen van een object en
- Expressies die een van deze bewerkingen combineren.
Gegevens sorteren
Gebruik .OrderBy()
, .OrderByDescending()
, .ThenBy()
en .ThenByDescending()
met een eigenschapstoegangsor om gegevens te sorteren.
var items = await remoteTable.OrderBy(x => x.IsComplete).ThenBy(x => x.Title).ToListAsync();
De sortering wordt uitgevoerd door de service. U kunt geen expressie opgeven in een sorteercomponent. Als u wilt sorteren op een expressie, gebruikt u sortering aan de clientzijde:
var items = await remoteTable.ToListAsync().OrderBy(x => x.Title.ToLowerCase());
Eigenschappen selecteren
U kunt een subset met gegevens van de service retourneren:
var items = await remoteTable.Select(x => new { x.Id, x.Title, x.IsComplete }).ToListAsync();
Een pagina met gegevens retourneren
U kunt een subset van de gegevensset retourneren met behulp van .Skip()
en .Take()
om paging te implementeren:
var pageOfItems = await remoteTable.Skip(100).Take(10).ToListAsync();
In een echte app kunt u query's gebruiken die vergelijkbaar zijn met het voorgaande voorbeeld met een pager-besturingselement of vergelijkbare gebruikersinterface om tussen pagina's te navigeren.
Alle functies die tot nu toe worden beschreven, zijn additief, zodat we ze kunnen blijven koppelen. Elke gekoppelde aanroep beïnvloedt meer van de query. Nog een voorbeeld:
var query = todoTable
.Where(todoItem => todoItem.Complete == false)
.Select(todoItem => todoItem.Text)
.Skip(3).
.Take(3);
List<string> items = await query.ToListAsync();
Externe gegevens opzoeken op id
De functie GetItemAsync
kan worden gebruikt om objecten uit de database op te zoeken met een bepaalde id.
TodoItem item = await remoteTable.GetItemAsync("37BBF396-11F0-4B39-85C8-B319C729AF6D");
Als het item dat u probeert op te halen voorlopig is verwijderd, moet u de parameter includeDeleted
gebruiken:
// 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);
Gegevens invoegen op de externe server
Alle clienttypen moeten een lid met de naam idbevatten. Dit is standaard een tekenreeks. Deze id- is vereist voor het uitvoeren van CRUD-bewerkingen en voor offlinesynchronisatie. De volgende code illustreert hoe u de methode InsertItemAsync
gebruikt om nieuwe rijen in een tabel in te voegen. De parameter bevat de gegevens die moeten worden ingevoegd als een .NET-object.
var item = new TodoItem { Title = "Text", IsComplete = false };
await remoteTable.InsertItemAsync(item);
// Note that item.Id will now be set
Als een unieke aangepaste id-waarde niet is opgenomen in de item
tijdens een invoeging, genereert de server een id. U kunt de gegenereerde id ophalen door het object te inspecteren nadat de aanroep is geretourneerd.
Gegevens op de externe server bijwerken
De volgende code laat zien hoe u de methode ReplaceItemAsync
gebruikt om een bestaande record bij te werken met dezelfde id met nieuwe informatie.
// In this example, we assume the item has been created from the InsertItemAsync sample
item.IsComplete = true;
await remoteTable.ReplaceItemAsync(todoItem);
Gegevens op de externe server verwijderen
De volgende code illustreert hoe u de DeleteItemAsync
methode gebruikt om een bestaand exemplaar te verwijderen.
// In this example, we assume the item has been created from the InsertItemAsync sample
await todoTable.DeleteItemAsync(item);
Conflictoplossing en optimistische gelijktijdigheid
Twee of meer clients kunnen tegelijkertijd wijzigingen naar hetzelfde item schrijven. Zonder conflictdetectie overschrijft de laatste schrijfbewerking eventuele eerdere updates. Optimistisch gelijktijdigheidsbeheer ervan uitgaat dat elke transactie kan worden doorgevoerd en daarom geen resourcevergrendeling gebruikt. Optimistisch gelijktijdigheidsbeheer controleert of er geen andere transactie de gegevens heeft gewijzigd voordat de gegevens worden doorgevoerd. Als de gegevens zijn gewijzigd, wordt de transactie teruggedraaid.
Azure Mobile Apps biedt ondersteuning voor optimistisch gelijktijdigheidsbeheer door wijzigingen in elk item bij te houden met behulp van de kolom version
systeemeigenschap die is gedefinieerd voor elke tabel in de back-end van uw mobiele app. Telkens wanneer een record wordt bijgewerkt, stelt Mobile Apps de eigenschap version
voor die record in op een nieuwe waarde. Tijdens elke updateaanvraag wordt de eigenschap version
van de record die bij de aanvraag is opgenomen, vergeleken met dezelfde eigenschap voor de record op de server. Als de versie die is doorgegeven met de aanvraag niet overeenkomt met de back-end, genereert de clientbibliotheek een DatasyncConflictException<T>
uitzondering. Het type dat is opgenomen met de uitzondering, is de record van de back-end met de serverversie van de record. De toepassing kan deze informatie vervolgens gebruiken om te bepalen of de updateaanvraag opnieuw moet worden uitgevoerd met de juiste version
waarde van de back-end om wijzigingen door te voeren.
Optimistische gelijktijdigheid wordt automatisch ingeschakeld wanneer u het DatasyncClientData
basisobject gebruikt.
Naast het inschakelen van optimistische gelijktijdigheid moet u ook de DatasyncConflictException<T>
uitzondering in uw code ondervangen. Los het conflict op door de juiste version
toe te passen op de bijgewerkte record en herhaal vervolgens de aanroep met de opgeloste record. De volgende code laat zien hoe u een schrijfconflict kunt oplossen nadat dit is gedetecteerd:
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();
}
Werken met offlinetabellen
Offlinetabellen gebruiken een lokaal SQLite-archief om gegevens op te slaan voor gebruik wanneer ze offline zijn. Alle tabelbewerkingen worden uitgevoerd op basis van het lokale SQLite-archief in plaats van het externe serverarchief. Zorg ervoor dat u de Microsoft.Datasync.Client.SQLiteStore
toevoegt aan elk platformproject en aan alle gedeelde projecten.
Voordat u een tabelreferentie kunt maken, moet het lokale archief worden voorbereid:
var store = new OfflineSQLiteStore(Constants.OfflineConnectionString);
store.DefineTable<TodoItem>();
Zodra de store is gedefinieerd, kunt u de client maken:
var options = new DatasyncClientOptions
{
OfflineStore = store
};
var client = new DatasyncClient("MOBILE_URL", options);
Ten slotte moet u ervoor zorgen dat de offlinemogelijkheden zijn geïnitialiseerd:
await client.InitializeOfflineStoreAsync();
De initialisatie van de opslag wordt normaal gesproken onmiddellijk uitgevoerd nadat de client is gemaakt. De OfflineConnectionString- is een URI die wordt gebruikt voor het opgeven van zowel de locatie van de SQLite-database als de opties die worden gebruikt om de database te openen. Zie URI-bestandsnamen in SQLitevoor meer informatie.
- Als u een cache in het geheugen wilt gebruiken, gebruikt u
file:inmemory.db?mode=memory&cache=private
. - Als u een bestand wilt gebruiken, gebruikt u
file:/path/to/file.db
U moet de absolute bestandsnaam voor het bestand opgeven. Als u Xamarin gebruikt, kunt u de Xamarin Essentials File System Helpers gebruiken om een pad te maken: bijvoorbeeld:
var dbPath = $"{Filesystem.AppDataDirectory}/todoitems.db";
var store = new OfflineSQLiteStore($"file:/{dbPath}?mode=rwc");
Als u GEBRUIKMAAKT van MAUI, kunt u de HELP-helpers van HET BESTANDSSYSTEEM VAN gebruiken om een pad te maken: bijvoorbeeld:
var dbPath = $"{Filesystem.AppDataDirectory}/todoitems.db";
var store = new OfflineSQLiteStore($"file:/{dbPath}?mode=rwc");
Een offlinetabel maken
U kunt een tabelreferentie verkrijgen met behulp van de methode GetOfflineTable<T>
:
IOfflineTable<TodoItem> table = client.GetOfflineTable<TodoItem>();
Net als bij de externe tabel kunt u ook een alleen-lezen offlinetabel beschikbaar maken:
IReadOnlyOfflineTable<TodoItem> table = client.GetOfflineTable<TodoItem>();
U hoeft zich niet te verifiëren voor het gebruik van een offlinetabel. U hoeft alleen te verifiëren wanneer u communiceert met de back-endservice.
Een offlinetabel synchroniseren
Offlinetabellen worden niet standaard gesynchroniseerd met de back-end. Synchronisatie wordt gesplitst in twee delen. U kunt wijzigingen afzonderlijk pushen van het downloaden van nieuwe items. Bijvoorbeeld:
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"]);
}
}
}
Standaard maken alle tabellen gebruik van incrementele synchronisatie. Alleen nieuwe records worden opgehaald. Er wordt een record opgenomen voor elke unieke query (gegenereerd door het maken van een MD5-hash van de OData-query).
Notitie
Het eerste argument voor PullItemsAsync
is de OData-query die aangeeft welke records naar het apparaat moeten worden opgehaald. Het is beter om de service zo te wijzigen dat alleen records worden geretourneerd die specifiek zijn voor de gebruiker in plaats van complexe query's aan de clientzijde te maken.
De opties (gedefinieerd door het PullOptions
-object) hoeven doorgaans niet te worden ingesteld. Opties zijn onder andere:
-
PushOtherTables
: als deze optie is ingesteld op waar, worden alle tabellen gepusht. -
QueryId
: een specifieke query-id die moet worden gebruikt in plaats van de gegenereerde id. -
WriteDeltaTokenInterval
: hoe vaak het deltatoken moet worden geschreven dat wordt gebruikt om incrementele synchronisatie bij te houden.
De SDK voert een impliciete PushAsync()
uit voordat records worden opgehaald.
Conflictafhandeling vindt plaats op een PullAsync()
methode. Conflicten op dezelfde manier verwerken als onlinetabellen. Het conflict wordt veroorzaakt wanneer PullAsync()
wordt aangeroepen in plaats van tijdens het invoegen, bijwerken of verwijderen. Als er meerdere conflicten optreden, worden ze gebundeld in één PushFailedException
. Elke fout afzonderlijk afhandelen.
Wijzigingen voor alle tabellen pushen
Als u alle wijzigingen naar de externe server wilt pushen, gebruikt u:
await client.PushTablesAsync();
Als u wijzigingen wilt pushen voor een subset van tabellen, geeft u een IEnumerable<string>
op voor de PushTablesAsync()
methode:
var tablesToPush = new string[] { "TodoItem", "Notes" };
await client.PushTables(tablesToPush);
Gebruik de eigenschap client.PendingOperations
om het aantal bewerkingen te lezen dat moet worden gepusht naar de externe service. Deze eigenschap is null
wanneer er geen offlinearchief is geconfigureerd.
Complexe SQLite-query's uitvoeren
Als u complexe SQL-query's wilt uitvoeren voor de offlinedatabase, kunt u dit doen met behulp van de methode ExecuteQueryAsync()
. Als u bijvoorbeeld een SQL JOIN
-instructie wilt uitvoeren, definieert u een JObject
waarin de structuur van de retourwaarde wordt weergegeven en gebruikt u vervolgens 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.
De definitie is een set sleutel/waarden. De sleutels moeten overeenkomen met de veldnamen die de SQL-query retourneert en de waarden moeten de standaardwaarde van het verwachte type zijn. Gebruik 0L
voor getallen (lang), false
voor booleaanse waarden en string.Empty
voor alles.
SQLite heeft een beperkende set ondersteunde typen. Datum/tijden worden opgeslagen als het aantal milliseconden sinds het tijdvak om vergelijkingen toe te staan.
Gebruikers verifiëren
Met Azure Mobile Apps kunt u een verificatieprovider genereren voor het verwerken van verificatieaanroepen. Geef de verificatieprovider op bij het maken van de serviceclient:
AuthenticationProvider authProvider = GetAuthenticationProvider();
var client = new DatasyncClient("APP_URL", authProvider);
Wanneer verificatie is vereist, wordt de verificatieprovider aangeroepen om het token op te halen. Een algemene verificatieprovider kan worden gebruikt voor verificatie op basis van zowel verificatie op basis van autorisatie- als App Service-verificatie en verificatie op basis van autorisatie. Gebruik het volgende model:
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 */"
};
}
Verificatietokens worden in de cache opgeslagen in het geheugen (nooit naar het apparaat geschreven) en indien nodig vernieuwd.
Het Microsoft Identity Platform gebruiken
Met het Microsoft Identity Platform kunt u eenvoudig integreren met Microsoft Entra ID. Zie de zelfstudies aan de slag voor een volledige zelfstudie over het implementeren van Microsoft Entra-verificatie. De volgende code toont een voorbeeld van het ophalen van het toegangstoken:
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;
}
}
Zie de documentatie Microsoft Identity Platform voor meer informatie over het integreren van het Microsoft Identity Platform met ASP.NET 6.
Xamarin Essentials of MAUI WebAuthenticator gebruiken
Voor Azure App Service-verificatie kunt u de Xamarin Essentials WebAuthenticator- of de MAUI WebAuthenticator- gebruiken om een token op te halen:
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
};
}
De UserId
en DisplayName
zijn niet rechtstreeks beschikbaar bij het gebruik van Azure App Service-verificatie. Gebruik in plaats daarvan een luie aanvrager om de informatie op te halen uit het /.auth/me
-eindpunt:
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);
}
}
Geavanceerde onderwerpen
Entiteiten in de lokale database opschonen
Onder normale werking is het opschonen van entiteiten niet vereist. Het synchronisatieproces verwijdert verwijderde entiteiten en onderhoudt de vereiste metagegevens voor lokale databasetabellen. Er zijn echter situaties waarin het opschonen van entiteiten in de database nuttig is. Een dergelijk scenario is wanneer u een groot aantal entiteiten moet verwijderen en het efficiënter is om gegevens uit de tabel lokaal te wissen.
Als u records uit een tabel wilt opschonen, gebruikt u table.PurgeItemsAsync()
:
var query = table.CreateQuery();
var purgeOptions = new PurgeOptions();
await table.PurgeItermsAsync(query, purgeOptions, cancellationToken);
De query identificeert de entiteiten die uit de tabel moeten worden verwijderd. Identificeer de entiteiten die moeten worden opgeschoond met LINQ:
var query = table.CreateQuery().Where(m => m.Archived == true);
De klasse PurgeOptions
biedt instellingen voor het wijzigen van de opschoonbewerking:
-
DiscardPendingOperations
verwijdert alle bewerkingen die in behandeling zijn voor de tabel die zich in de wachtrij voor bewerkingen bevinden die wachten om naar de server te worden verzonden. -
QueryId
geeft een query-id op die wordt gebruikt om het deltatoken te identificeren dat moet worden gebruikt voor de bewerking. -
TimestampUpdatePolicy
geeft aan hoe u het deltatoken aan het einde van de opschoningsbewerking kunt aanpassen:-
TimestampUpdatePolicy.NoUpdate
geeft aan dat het deltatoken niet mag worden bijgewerkt. -
TimestampUpdatePolicy.UpdateToLastEntity
geeft aan dat het deltatoken moet worden bijgewerkt naar hetupdatedAt
veld voor de laatste entiteit die in de tabel is opgeslagen. -
TimestampUpdatePolicy.UpdateToNow
geeft aan dat het deltatoken moet worden bijgewerkt naar de huidige datum/tijd. -
TimestampUpdatePolicy.UpdateToEpoch
geeft aan dat het deltatoken opnieuw moet worden ingesteld om alle gegevens te synchroniseren.
-
Gebruik dezelfde QueryId
waarde die u hebt gebruikt bij het aanroepen van table.PullItemsAsync()
om gegevens te synchroniseren. De QueryId
geeft het deltatoken op dat moet worden bijgewerkt wanneer de opschoning is voltooid.
Aanvraagheaders aanpassen
Ter ondersteuning van uw specifieke app-scenario moet u mogelijk de communicatie aanpassen met de back-end van de mobiele app. U kunt bijvoorbeeld een aangepaste header toevoegen aan elke uitgaande aanvraag of antwoordstatuscodes wijzigen voordat u terugkeert naar de gebruiker. Gebruik een aangepaste DelegatingHandler, zoals in het volgende voorbeeld:
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;
}
}
Logboekregistratie van aanvragen inschakelen
U kunt ook een DelegatingHandler gebruiken om logboekregistratie van aanvragen toe te voegen:
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;
}
}
Synchronisatie-gebeurtenissen bewaken
Wanneer er een synchronisatiegebeurtenis plaatsvindt, wordt de gebeurtenis gepubliceerd naar de gemachtigde van de client.SynchronizationProgress
gebeurtenis. De gebeurtenissen kunnen worden gebruikt om de voortgang van het synchronisatieproces te controleren. Definieer als volgt een synchronisatie-gebeurtenis-handler:
client.SynchronizationProgress += (sender, args) => {
// args is of type SynchronizationEventArgs
};
Het SynchronizationEventArgs
type wordt als volgt gedefinieerd:
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; }
}
De eigenschappen in args
zijn null
of -1
wanneer de eigenschap niet relevant is voor de synchronisatie-gebeurtenis.