De Azure Mobile Apps-clientbibliotheek voor .NET gebruiken
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:
- BetekendeiaUI
- MAUI (Android en iOS)
- Uno-platform
- Windows (UWP)
- Windows (WinUI3)
- Windows (WPF)
- 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 v4.2.0-documentatie voor oudere clients.
Ondersteunde platformen
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).
- Universeel Windows-platform builds 19041 en hoger.
- Windows Presentation Framework (WPF).
- Windows-app SDK (WinUI 3).
- Xamarin.Forms
Daarnaast zijn er monsters gemaakt voor Den Enna 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 door MOBILE_APP_URL
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.
Verderop in dit artikel worden voorbeelden gegeven van het delegeren van handlers voor logboekregistratie en het toevoegen van aanvraagheaders .
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 IdGenerator
optie 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
set is ingesteld, wordt er een aangepaste header X-ZUMO-INSTALLATION-ID
verzonden met elke aanvraag 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 deze gebruikt InstallationId
, moet de id worden opgeslagen in permanente opslag op het apparaat, zodat unieke installaties kunnen worden bijgehouden.
OfflineStore
De OfflineStore
gegevens worden gebruikt bij het configureren van offlinegegevenstoegang. Zie Werken met offlinetabellen voor 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.
Serializer Instellingen
Als u de serializer-instellingen op de gegevenssynchronisatieserver hebt gewijzigd, moet u dezelfde wijzigingen aanbrengen in de SerializerSettings
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 Route
kenmerk in de servercode). Tabellen kunnen echter bestaan op elk eindpuntpad. Dit 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
:
var options = new DatasyncClientOptions
{
TableEndpointResolver = (table) => $"/api/{table}"
};
UserAgent
De gegevenssynchronisatieclient genereert een geschikte headerwaarde voor de gebruikersagent 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 UserAgent
eigenschap 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
- Query's uitvoeren op gegevens
- Items uit een query tellen
- Externe gegevens opzoeken op id
- Gegevens invoegen op de externe server
- Gegevens op de externe server bijwerken
- Gegevens op de externe server verwijderen
- 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 alleen-lezen tabel 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
dit 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 DataTable
kenmerk:
[DataTable("todoitem")]
public class MyTodoItemClass : DatasyncClientData
{
public string Title { get; set; }
public bool IsComplete { get; set; }
}
U kunt ook de naam van de tabel in de GetRemoteTable()
aanroep opgeven:
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 deze gebruiken .CountItemsAsync()
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>
naar een andere verzameling te converteren:
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 ConcurrentObservableCollection<T>
ook een thread-veilige verzameling waarneembare verzameling. Deze klasse kan worden gebruikt in de context van UI-toepassingen die normaal gesproken worden gebruikt ObservableCollection<T>
voor het beheren van een lijst (bijvoorbeeld Xamarin Forms of FORMS-lijsten). U kunt een ConcurrentObservableCollection<T>
tabel of query rechtstreeks uit een tabel of query wissen en laden:
var collection = new ConcurrentObservableCollection<T>();
await remoteTable.ToObservableCollection(collection);
Met het gebruik van .ToObservableCollection(collection)
triggers wordt de CollectionChanged
gebeurtenis eenmaal 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 'The') op de client wordt uitgevoerd.
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
alleenReplace
EndsWith
Substring
IndexOf
Equals
StartsWith
ordinale en invariante culturen), - Datumeigenschappen (
Year
,Month
,Day
,Hour
, ),Second
Minute
- 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 .Skip()
behulp van 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 GetItemAsync
functie 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 includeDeleted
parameter 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 Id bevatten. 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 InsertItemAsync
methode 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
invoegpositie, 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 illustreert hoe u de ReplaceItemAsync
methode 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 gaat ervan uit 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 version
kolom 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 version
eigenschap voor die record in op een nieuwe waarde. Tijdens elke updateaanvraag wordt de version
eigenschap van de record die is opgenomen in de aanvraag 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 het juiste version
toe te passen op de bijgewerkte record en herhaal de aanroep vervolgens 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 het Microsoft.Datasync.Client.SQLiteStore
aan elk platformproject en aan alle gedeelde projecten toevoegt.
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. Offline Verbinding maken ionString 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 SQLite voor 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 MAUI gebruiken om een pad te maken: bijvoorbeeld:
var dbPath = $"{Filesystem.AppDataDirectory}/todoitems.db";
var store = new OfflineSQLiteStore($"file:/{dbPath}?mode=rwc");
Een offlinetabel maken
Een tabelreferentie kan worden verkregen met behulp van de GetOfflineTable<T>
methode:
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 PullItemsAsync
hiervoor is de OData-query die aangeeft welke records moeten worden opgehaald naar het apparaat. 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. De volgende opties zijn beschikbaar:
PushOtherTables
- indien ingesteld op true, worden alle tabellen gepusht.QueryId
- een specifieke query-id die moet worden gebruikt in plaats van de gegenereerde id.WriteDeltaTokenInterval
- hoe vaak u het deltatoken schrijft dat wordt gebruikt om incrementele synchronisatie bij te houden.
De SDK voert een impliciete bewerking uit PushAsync()
voordat records worden opgehaald.
Conflictafhandeling vindt plaats op een PullAsync()
methode. Conflicten op dezelfde manier verwerken als onlinetabellen. Het conflict wordt veroorzaakt wanneer PullAsync()
deze 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>
aan de PushTablesAsync()
methode op:
var tablesToPush = new string[] { "TodoItem", "Notes" };
await client.PushTables(tablesToPush);
Gebruik de client.PendingOperations
eigenschap 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 ExecuteQueryAsync()
methode. Als u bijvoorbeeld een SQL JOIN
instructie wilt uitvoeren, definieert u een JObject
instructie die de structuur van de retourwaarde weergeeft en gebruikt u 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. Gebruiken 0L
voor getallen (lang), false
voor Booleaanse waarden en string.Empty
voor alles anders.
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 van het 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 serviceverificatie kunt u de Xamarin Essentials WebAuthenticator of de TENANT 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 serviceverificatie. Gebruik in plaats daarvan een luie aanvrager om de informatie op te halen van 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 PurgeOptions
klasse 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 table.PullItemsAsync()
om gegevens te synchroniseren. Hiermee QueryId
geeft u 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 binnen args
zijn of null
-1
wanneer de eigenschap niet relevant is voor de synchronisatie-gebeurtenis.