Jak používat klientskou knihovnu Azure Mobile Apps pro .NET

V této příručce se dozvíte, jak provádět běžné scénáře pomocí klientské knihovny .NET pro Azure Mobile Apps. Klientskou knihovnu .NET použijte v libovolné aplikaci .NET 6 nebo .NET Standard 2.0, včetně MAUI, Xamarinu a Windows (WPF, UPW a WinUI).

Pokud s Azure Mobile Apps teprve začínáte, zvažte první dokončení některého z kurzů pro rychlý start:

Poznámka:

Tento článek popisuje nejnovější edici rozhraní Microsoft Datasync Framework (v6.0). Starší klienti najdete v dokumentaci k verzi 4.2.0.

Podporované platformy

Klientská knihovna .NET podporuje libovolnou platformu .NET Standard 2.0 nebo .NET 6, včetně:

  • Platformy .NET MAUI pro Android, iOS a Windows
  • Android API úrovně 21 a novější (Xamarin a Android pro .NET).
  • iOS verze 12.0 a novější (Xamarin a iOS pro .NET).
  • Univerzální platforma Windows buildy 19041 a novější.
  • Windows Presentation Framework (WPF).
  • Windows App SDK (WinUI 3).
  • Xamarin.Forms

Kromě toho byly vytvořeny vzorky pro Avalonia a Uno Platform. Ukázka TodoApp obsahuje příklad každé testované platformy.

Nastavení a požadavky

Přidejte následující knihovny z NuGetu:

Pokud používáte projekt platformy (například .NET MAUI), ujistěte se, že do projektu platformy a všech sdílených projektů přidáte knihovny.

Vytvoření klienta služby

Následující kód vytvoří klienta služby, který slouží ke koordinaci veškeré komunikace s back-endovými a offline tabulkami.

var options = new DatasyncClientOptions 
{
    // Options set here
};
var client = new DatasyncClient("MOBILE_APP_URL", options);

V předchozím kódu nahraďte MOBILE_APP_URL adresou URL back-endu ASP.NET Core. Klient by se měl vytvořit jako jednoúčelový. Pokud používáte zprostředkovatele ověřování, můžete ho nakonfigurovat takto:

var options = new DatasyncClientOptions 
{
    // Options set here
};
var client = new DatasyncClient("MOBILE_APP_URL", authProvider, options);

Další podrobnosti o zprostředkovateli ověřování najdete dále v tomto dokumentu.

Možnosti

Úplnou (výchozí) sadu možností je možné vytvořit takto:

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

Za normálních okolností se požadavek HTTP provádí předáním požadavku prostřednictvím zprostředkovatele ověřování (který před odesláním požadavku přidá hlavičku Authorization pro aktuálně ověřeného uživatele). Volitelně můžete přidat další obslužné rutiny delegování. Každý požadavek před odesláním do služby prochází delegujícími obslužné rutiny. Delegování obslužných rutin umožňuje přidávat další hlavičky, provádět opakování nebo poskytovat možnosti protokolování.

Příklady delegování obslužných rutin jsou k dispozici pro protokolování a přidání hlaviček požadavků dále v tomto článku.

IdGenerator

Když je entita přidána do offline tabulky, musí mít ID. ID se vygeneruje, pokud ho nezadáte. Tato IdGenerator možnost umožňuje přizpůsobit ID, které se vygeneruje. Ve výchozím nastavení se vygeneruje globálně jedinečné ID. Například následující nastavení vygeneruje řetězec, který obsahuje název tabulky a identifikátor GUID:

var options = new DatasyncClientOptions 
{
    IdGenerator = (table) => $"{table}-{Guid.NewGuid().ToString("D").ToUpperInvariant()}"
}

ID instalace

Pokud je nastavená InstallationId sada, odešle se s každou žádostí vlastní hlavička X-ZUMO-INSTALLATION-ID , která identifikuje kombinaci aplikace na konkrétním zařízení. Tato hlavička se dá zaznamenat v protokolech a umožňuje určit počet jedinečných instalací vaší aplikace. Pokud používáte InstallationId, mělo by být ID uloženo v trvalém úložišti na zařízení, aby bylo možné sledovat jedinečné instalace.

Offlinestore

Používá OfflineStore se při konfiguraci přístupu k offline datům. Další informace najdete v tématu Práce s offline tabulkami.

ParallelOperations

Součástí procesu offline synchronizace je nabízení operací zařazených do fronty na vzdálený server. Při aktivaci operace nabízení se operace odesílají v pořadí, v jakém byly přijaty. Volitelně můžete k nasdílení těchto operací použít až osm vláken. Paralelní operace používají více prostředků na klientovi i serveru k rychlejšímu dokončení operace. Pořadí, ve kterém operace přicházejí na server, nelze zaručit při použití více vláken.

Serializátor Nastavení

Pokud jste na serveru synchronizace dat změnili nastavení serializátoru, musíte provést stejné změny v SerializerSettings klientovi. Tato možnost umožňuje zadat vlastní nastavení serializátoru.

TableEndpointResolver

Podle konvence se tabulky nacházejí ve vzdálené službě /tables/{tableName} v cestě (jak je specifikováno atributem Route v kódu serveru). Tabulky však mohou existovat v libovolné cestě koncového bodu. Jedná se TableEndpointResolver o funkci, která změní název tabulky na cestu pro komunikaci se vzdálenou službou.

Například následující změna předpokladu tak, aby všechny tabulky byly umístěny v /api:

var options = new DatasyncClientOptions
{
    TableEndpointResolver = (table) => $"/api/{table}"
};

UserAgent

Klient synchronizace dat vygeneruje vhodnou hodnotu hlavičky user-agenta na základě verze knihovny. Někteří vývojáři cítí, že hlavička uživatelského agenta nevrací informace o klientovi. Vlastnost můžete nastavit na libovolnou platnou UserAgent hodnotu záhlaví.

Práce se vzdálenými tabulkami

Následující část podrobně popisuje, jak vyhledávat a načítat záznamy a upravovat data ve vzdálené tabulce. Probírána jsou následující témata:

Vytvoření odkazu na vzdálenou tabulku

Pokud chcete vytvořit odkaz na vzdálenou tabulku, použijte GetRemoteTable<T>:

IRemoteTable<TodoItem> remoteTable = client.GetRemoteTable();

Pokud chcete vrátit tabulku jen pro čtení, použijte IReadOnlyRemoteTable<T> verzi:

IReadOnlyRemoteTable<TodoItem> remoteTable = client.GetRemoteTable();

Typ modelu musí implementovat ITableData kontrakt ze služby. Slouží DatasyncClientData k zadání požadovaných polí:

public class TodoItem : DatasyncClientData
{
    public string Title { get; set; }
    public bool IsComplete { get; set; }
}

Objekt DatasyncClientData zahrnuje:

  • Id (string) – globálně jedinečné ID položky.
  • UpdatedAt (System.DataTimeOffset) – datum a čas poslední aktualizace položky.
  • Version (string) – neprůzný řetězec použitý pro správu verzí.
  • Deleted (logická hodnota) – pokud truese položka odstraní.

Služba tato pole udržuje. Tato pole neupravujte jako součást klientské aplikace.

Modely je možné anotovat pomocí atributů Newtonsoft.JSON. Název tabulky lze zadat pomocí atributu DataTable :

[DataTable("todoitem")]
public class MyTodoItemClass : DatasyncClientData
{
    public string Title { get; set; }
    public bool IsComplete { get; set; }
}

Případně zadejte název tabulky ve GetRemoteTable() volání:

IRemoteTable<TodoItem> remoteTable = client.GetRemoteTable("todoitem");

Klient používá cestu /tables/{tablename} jako identifikátor URI. Název tabulky je také název offline tabulky v databázi SQLite.

Podporované typy

Kromě primitivních typů (int, float, string atd.) jsou pro modely podporovány následující typy:

  • System.DateTime - jako řetězec data a času UTC iso-8601 s přesností ms.
  • System.DateTimeOffset - jako řetězec data a času UTC iso-8601 s přesností ms.
  • System.Guid - formátován jako 32 číslic oddělených jako pomlčky.

Dotazování dat ze vzdáleného serveru

Vzdálená tabulka se dá použít s příkazy typu LINQ, včetně:

  • Filtrování pomocí .Where() klauzule
  • Řazení pomocí různých .OrderBy() klauzulí
  • Výběr vlastností pomocí .Select()příkazu .
  • Stránkování s .Skip() a .Take().

Počítání položek z dotazu

Pokud potřebujete počet položek, které by dotaz vrátil, můžete použít .CountItemsAsync() v tabulce nebo .LongCountAsync() dotazu:

// 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();

Tato metoda způsobí odezvu na server. Můžete také získat počet při vyplňování seznamu (například), abyste se vyhnuli dodatečnému odezvě:

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);
}

Počet se vyplní po prvním požadavku na načtení obsahu tabulky.

Vrácení všech dat

Data se vrací prostřednictvím IAsyncEnumerable:

var enumerable = remoteTable.ToAsyncEnumerable();
await foreach (var item in enumerable) 
{
    // Process each item
}

K převodu IAsyncEnumerable<T> na jinou kolekci použijte některou z následujících ukončovacích klauzulí:

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();

Vzdálená tabulka na pozadí zpracovává stránkování výsledku za vás. Všechny položky se vrátí bez ohledu na to, kolik požadavků na straně serveru je potřeba k splnění dotazu. Tyto prvky jsou také k dispozici pro výsledky dotazu (například remoteTable.Where(m => m.Rating == "R")).

Architektura synchronizace dat také poskytuje ConcurrentObservableCollection<T> pozorovatelnou kolekci bezpečnou pro přístup z více vláken. Tuto třídu lze použít v kontextu aplikací uživatelského rozhraní, které by normálně používaly ObservableCollection<T> ke správě seznamu (například seznamy Xamarin Forms nebo MAUI). Můžete vymazat a načíst ConcurrentObservableCollection<T> přímo z tabulky nebo dotazu:

var collection = new ConcurrentObservableCollection<T>();
await remoteTable.ToObservableCollection(collection);

Použití .ToObservableCollection(collection) aktivační události CollectionChanged jednou pro celou kolekci místo pro jednotlivé položky, což vede k rychlejšímu překreslení času.

Obsahuje ConcurrentObservableCollection<T> také predikátové úpravy:

// 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);

Predikátové úpravy lze použít v obslužných rutinách událostí, pokud index položky není předem známý.

Filtrování dat

K filtrování dat můžete použít .Where() klauzuli. Příklad:

var items = await remoteTable.Where(x => !x.IsComplete).ToListAsync();

Filtrování se provádí ve službě před IAsyncEnumerable a v klientovi po IAsyncEnumerable. Příklad:

var items = (await remoteTable.Where(x => !x.IsComplete).ToListAsync()).Where(x => x.Title.StartsWith("The"));

.Where() První klauzule (vrácení pouze neúplných položek) se provádí ve službě, zatímco druhá .Where() klauzule (počínaje "The") se provádí v klientovi.

Klauzule Where podporuje operace, které se přeloží do podmnožině OData. Mezi operace patří:

  • Relační operátory (==, !=, <=<, >), >=
  • Aritmetické operátory (+, -, /, *, %),
  • Přesnost čísla (Math.Floor, Math.Ceiling),
  • Řetězcové funkce (Length, Substring, Replace, IndexOf, , Equals, , StartsWith) EndsWith(pouze řadové a invariantní jazykové verze),
  • Vlastnosti data (Year, Month, Day, Hour, Minute, Second),
  • Přístup k vlastnostem objektu a
  • Výrazy kombinující kteroukoli z těchto operací.

Řazení dat

K řazení dat použijte .OrderBy(), .OrderByDescending(), .ThenBy()a .ThenByDescending() s objektem vlastnosti.

var items = await remoteTable.OrderBy(x => x.IsComplete).ThenBy(x => x.Title).ToListAsync();

Řazení provádí služba. Výraz nelze zadat v žádné klauzuli řazení. Pokud chcete řadit podle výrazu, použijte řazení na straně klienta:

var items = await remoteTable.ToListAsync().OrderBy(x => x.Title.ToLowerCase());

Výběr vlastností

Ze služby můžete vrátit podmnožinu dat:

var items = await remoteTable.Select(x => new { x.Id, x.Title, x.IsComplete }).ToListAsync();

Vrácení stránky dat

Můžete vrátit podmnožinu datové sady pomocí .Skip() a .Take() implementovat stránkování:

var pageOfItems = await remoteTable.Skip(100).Take(10).ToListAsync();

V reálné aplikaci můžete k navigaci mezi stránkami použít dotazy podobné předchozímu příkladu pomocí ovládacího prvku pager nebo srovnatelného uživatelského rozhraní.

Všechny dosud popsané funkce jsou přídatné, takže je můžeme dál zřetězovat. Každé zřetězený volání ovlivňuje více dotazu. Jeden další příklad:

var query = todoTable
                .Where(todoItem => todoItem.Complete == false)
                .Select(todoItem => todoItem.Text)
                .Skip(3).
                .Take(3);
List<string> items = await query.ToListAsync();

Vyhledání vzdálených dat podle ID

Funkci GetItemAsync lze použít k vyhledání objektů z databáze s konkrétním ID.

TodoItem item = await remoteTable.GetItemAsync("37BBF396-11F0-4B39-85C8-B319C729AF6D");

Pokud byla položka, kterou se pokoušíte načíst, obnovitelně odstraněna, musíte použít includeDeleted parametr:

// 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);

Vložení dat na vzdálený server

Všechny typy klientů musí obsahovat id člena s názvem, což je ve výchozím nastavení řetězec. Toto ID se vyžaduje k provádění operací CRUD a k offline synchronizaci. Následující kód ukazuje, jak pomocí InsertItemAsync metody vložit nové řádky do tabulky. Parametr obsahuje data, která se mají vložit jako objekt .NET.

var item = new TodoItem { Title = "Text", IsComplete = false };
await remoteTable.InsertItemAsync(item);
// Note that item.Id will now be set

Pokud během vkládání není zahrnuta item jedinečná hodnota vlastního ID, server vygeneruje ID. Vygenerované ID můžete načíst kontrolou objektu po vrácení volání.

Aktualizace dat na vzdáleném serveru

Následující kód ukazuje, jak použít metodu ReplaceItemAsync k aktualizaci existujícího záznamu se stejným ID s novými informacemi.

// In this example, we assume the item has been created from the InsertItemAsync sample

item.IsComplete = true;
await remoteTable.ReplaceItemAsync(todoItem);

Odstranění dat na vzdáleném serveru

Následující kód ukazuje, jak pomocí DeleteItemAsync metody odstranit existující instanci.

// In this example, we assume the item has been created from the InsertItemAsync sample

await todoTable.DeleteItemAsync(item);

Řešení konfliktů a optimistická souběžnost

Dva nebo více klientů můžou zapisovat změny do stejné položky současně. Bez detekce konfliktů by poslední zápis přepsal všechny předchozí aktualizace. Optimistická kontrola souběžnosti předpokládá, že každá transakce může potvrdit, a proto nepoužívá žádné uzamčení prostředků. Optimistická kontrola souběžnosti ověřuje, že žádná jiná transakce neupravila data před potvrzením dat. Pokud se data změnila, transakce se vrátí zpět.

Azure Mobile Apps podporuje optimistické řízení souběžnosti sledováním změn jednotlivých položek pomocí version sloupce systémových vlastností, který je definovaný pro každou tabulku v back-endu mobilní aplikace. Při každé aktualizaci záznamu nastaví version Mobile Apps vlastnost pro tento záznam na novou hodnotu. Během každé žádosti o version aktualizaci se vlastnost záznamu zahrnutého do požadavku porovná se stejnou vlastností záznamu na serveru. Pokud se verze předaná požadavkem neshoduje s back-endem, klientská knihovna vyvolá DatasyncConflictException<T> výjimku. Typ zahrnutý s výjimkou je záznam z back-endu obsahujícího verzi serveru záznamu. Aplikace pak může pomocí těchto informací rozhodnout, jestli se má žádost o aktualizaci spustit znovu se správnou version hodnotou z back-endu a potvrdit změny.

Optimistická souběžnost se při použití základního objektu DatasyncClientData automaticky povolí.

Kromě povolení optimistické souběžnosti musíte také zachytit DatasyncConflictException<T> výjimku v kódu. Vyřešte konflikt tím, že na aktualizovaný záznam použijete správnou odpověď version a pak volání zopakujete s vyřešeným záznamem. Následující kód ukazuje, jak po zjištění vyřešit konflikt zápisu:

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();
}

Práce s offline tabulkami

Offline tabulky používají místní úložiště SQLite k ukládání dat pro použití v offline režimu. Všechny operace tabulek se provádějí s místním úložištěm SQLite místo vzdáleného úložiště serveru. Nezapomeňte přidat Microsoft.Datasync.Client.SQLiteStore projekt každé platformy a do všech sdílených projektů.

Před vytvořením odkazu na tabulku musí být místní úložiště připravené:

var store = new OfflineSQLiteStore(Constants.OfflineConnectionString);
store.DefineTable<TodoItem>();

Po definování úložiště můžete vytvořit klienta:

var options = new DatasyncClientOptions 
{
    OfflineStore = store
};
var client = new DatasyncClient("MOBILE_URL", options);

Nakonec musíte zajistit, aby se inicializovaly možnosti offline:

await client.InitializeOfflineStoreAsync();

Inicializace úložiště se obvykle provádí okamžitě po vytvoření klienta. Offline Připojení ionString je identifikátor URI, který slouží k určení umístění databáze SQLite i možností použitých k otevření databáze. Další informace naleznete v tématu Názvy souborů URI v SQLite.

  • Chcete-li použít mezipaměť v paměti, použijte file:inmemory.db?mode=memory&cache=private.
  • Pokud chcete použít soubor, použijte file:/path/to/file.db

Musíte zadat absolutní název souboru. Pokud používáte Xamarin, můžete pomocí pomocných rutin systému souborů Xamarin Essentials vytvořit cestu: Například:

var dbPath = $"{Filesystem.AppDataDirectory}/todoitems.db";
var store = new OfflineSQLiteStore($"file:/{dbPath}?mode=rwc");

Pokud používáte MAUI, můžete k vytvoření cesty použít pomocné rutiny systému souborů MAUI: Například:

var dbPath = $"{Filesystem.AppDataDirectory}/todoitems.db";
var store = new OfflineSQLiteStore($"file:/{dbPath}?mode=rwc");

Vytvoření offline tabulky

Pomocí metody lze získat odkaz na GetOfflineTable<T> tabulku:

IOfflineTable<TodoItem> table = client.GetOfflineTable<TodoItem>();

Stejně jako u vzdálené tabulky můžete také vystavit offline tabulku jen pro čtení:

IReadOnlyOfflineTable<TodoItem> table = client.GetOfflineTable<TodoItem>();

Abyste mohli používat offline tabulku, nemusíte se ověřovat. Při komunikaci s back-endovou službou je potřeba provést ověření.

Synchronizace offline tabulky

Offline tabulky se ve výchozím nastavení nesynchronují s back-endem. Synchronizace je rozdělená na dvě části. Změny můžete odesílat odděleně od stahování nových položek. Příklad:

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"]);
        }
    }
}

Ve výchozím nastavení používají všechny tabulky přírůstkovou synchronizaci – načtou se jenom nové záznamy. Záznam je součástí každého jedinečného dotazu (vygenerovaného vytvořením hodnoty hash MD5 dotazu OData).

Poznámka:

Prvním argumentem PullItemsAsync je dotaz OData, který označuje, které záznamy se mají na zařízení vyžádat. Je lepší službu upravit tak, aby vracela jenom záznamy specifické pro uživatele, a ne vytvářet složité dotazy na straně klienta.

Možnosti (definované objektem PullOptions ) se obvykle nemusí nastavovat. K dispozici jsou následující možnosti:

  • PushOtherTables – pokud je nastavena hodnota true, všechny tabulky se nasdílí.
  • QueryId – konkrétní ID dotazu, které se má použít místo vygenerovaného.
  • WriteDeltaTokenInterval – jak často se má zapisovat rozdílový token použitý ke sledování přírůstkové synchronizace.

Sada SDK provádí implicitní PushAsync() před vyžádáním záznamů.

Zpracování konfliktů probíhá u PullAsync() metody. Zpracovává konflikty stejným způsobem jako online tabulky. Konflikt se vytvoří, když PullAsync() se volá místo během vkládání, aktualizace nebo odstranění. Pokud dojde k několika konfliktům, jsou spojeny do jednoho PushFailedException. Zpracujte jednotlivé chyby samostatně.

Nasdílení změn pro všechny tabulky

Pokud chcete odeslat všechny změny na vzdálený server, použijte:

await client.PushTablesAsync();

Pokud chcete odeslat změny pro podmnožinu tabulek, zadejte metodu IEnumerable<string>PushTablesAsync() :

var tablesToPush = new string[] { "TodoItem", "Notes" };
await client.PushTables(tablesToPush);

client.PendingOperations Pomocí vlastnosti můžete přečíst počet operací čekajících na odeslání do vzdálené služby. Tato vlastnost je null v případě, že není nakonfigurované žádné offline úložiště.

Spouštění složitých dotazů SQLite

Pokud potřebujete provádět složité dotazy SQL na offline databázi, můžete to provést pomocí ExecuteQueryAsync() této metody. Pokud chcete například provést SQL JOIN příkaz, definujte JObject strukturu návratové hodnoty a pak použijte 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.

Definice je sada klíčů/hodnot. Klíče musí odpovídat názvům polí, které dotaz SQL vrátí, a hodnoty musí být výchozí hodnotou očekávaného typu. Slouží 0L pro čísla (dlouhá), false pro logické hodnoty a string.Empty pro všechno ostatní.

SQLite má omezující sadu podporovaných typů. Datum a časy se ukládají jako počet milisekund od epochy, aby bylo možné porovnávat.

Ověření uživatelů

Azure Mobile Apps umožňuje vygenerovat zprostředkovatele ověřování pro zpracování volání ověřování. Při vytváření klienta služby zadejte zprostředkovatele ověřování:

AuthenticationProvider authProvider = GetAuthenticationProvider();
var client = new DatasyncClient("APP_URL", authProvider);

Pokaždé, když se vyžaduje ověřování, zavolá se zprostředkovatel ověřování, který token získá. Pro ověřování založené na autorizační hlavičce i ověřování na základě autorizace a ověřování na základě autorizace je možné použít obecného zprostředkovatele ověřování. Použijte následující 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 */"
    };
}

Ověřovací tokeny se ukládají do mezipaměti (nikdy se nezapisují do zařízení) a v případě potřeby se aktualizují.

Použití platformy Microsoft Identity Platform

Platforma Microsoft Identity Platform umožňuje snadnou integraci s ID Microsoft Entra. Kompletní kurz implementace ověřování Microsoft Entra najdete v úvodních kurzech. Následující kód ukazuje příklad načtení přístupového tokenu:

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;
    }
}

Další informace o integraci platformy Microsoft Identity Platform s ASP.NET 6 najdete v dokumentaci k platformě Microsoft Identity Platform .

Použití Xamarin Essentials nebo MAUI WebAuthenticator

Pro ověřování služby Aplikace Azure můžete k získání tokenu použít Xamarin Essentials WebAuthenticator nebo MAUI WebAuthenticator:

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
    };
}

Pokud UserId používáte ověřování služby Aplikace Azure, nejsou k DisplayName dispozici přímo. Místo toho použijte opožděný žadatel k načtení informací z koncového /.auth/me bodu:

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);
    }
}

Pokročilá témata

Vymazání entit v místní databázi

V normálním provozu není potřeba vyprázdnit entity. Proces synchronizace odebere odstraněné entity a udržuje požadovaná metadata pro tabulky místní databáze. Existují ale chvíle, kdy je užitečné vyprázdnit entity v databázi. Jedním z takových scénářů je, když potřebujete odstranit velký počet entit a je efektivnější vymazat data z tabulky místně.

Pokud chcete vymazat záznamy z tabulky, použijte table.PurgeItemsAsync():

var query = table.CreateQuery();
var purgeOptions = new PurgeOptions();
await table.PurgeItermsAsync(query, purgeOptions, cancellationToken);

Dotaz identifikuje entity, které se mají z tabulky odebrat. Identifikujte entity, které se mají vyprázdnit pomocí LINQ:

var query = table.CreateQuery().Where(m => m.Archived == true);

Třída PurgeOptions poskytuje nastavení pro úpravu operace vyprázdnění:

  • DiscardPendingOperations zahodí všechny čekající operace pro tabulku, která je ve frontě operací čekajících na odeslání na server.
  • QueryId určuje ID dotazu, které slouží k identifikaci rozdílového tokenu, který se má pro operaci použít.
  • TimestampUpdatePolicy určuje, jak upravit rozdílový token na konci operace vyprázdnění:
    • TimestampUpdatePolicy.NoUpdate označuje, že rozdílový token nesmí být aktualizován.
    • TimestampUpdatePolicy.UpdateToLastEntity označuje, že by se rozdílový token měl aktualizovat na updatedAt pole poslední entity uložené v tabulce.
    • TimestampUpdatePolicy.UpdateToNow označuje, že by se rozdílový token měl aktualizovat na aktuální datum a čas.
    • TimestampUpdatePolicy.UpdateToEpoch označuje, že rozdílový token by se měl resetovat, aby se synchronizovala všechna data.

Použijte stejnou QueryId hodnotu, jakou jste použili při volání table.PullItemsAsync() k synchronizaci dat. Určuje QueryId rozdílový token, který se má aktualizovat po dokončení vyprázdnění.

Přizpůsobení hlaviček požadavků

Pokud chcete podporovat konkrétní scénář aplikace, možná budete muset přizpůsobit komunikaci s back-endem mobilní aplikace. Můžete například přidat vlastní hlavičku do každého odchozího požadavku nebo změnit stavové kódy odpovědi před návratem k uživateli. Použijte vlastní DelegováníHandler, jak je znázorněno v následujícím příkladu:

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;
    }
}

Povolení protokolování požadavků

Protokolování požadavku můžete přidat také pomocí delegační obslužné rutiny:

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;
    }
}

Monitorování událostí synchronizace

Když dojde k události synchronizace, událost se publikuje do delegáta client.SynchronizationProgress události. Události lze použít ke sledování průběhu procesu synchronizace. Obslužnou rutinu události synchronizace definujte následujícím způsobem:

client.SynchronizationProgress += (sender, args) => {
    // args is of type SynchronizationEventArgs
};

Typ SynchronizationEventArgs je definován takto:

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; }
}

Vlastnosti v rámci args jsou buď null nebo -1 pokud tato vlastnost není relevantní pro událost synchronizace.