Az Azure Mobile Apps ügyfélkódtár használata .NET-hez

Ez az útmutató bemutatja, hogyan hajthat végre gyakori forgatókönyveket az Azure Mobile Apps .NET-ügyfélkódtárával. A .NET-ügyfélkódtárat bármely .NET 6 vagy .NET Standard 2.0-alkalmazásban használhatja, beleértve a MAUI-t, a Xamarint és a Windowst (WPF, UWP és WinUI).

Ha még csak most ismerkedik az Azure Mobile Apps szolgáltatással, fontolja meg az alábbi rövid útmutatók egyikének elvégzését:

Megjegyzés:

Ez a cikk a Microsoft Datasync Framework legújabb (6.0-s) kiadását ismerteti. Régebbi ügyfelek esetén lásd a v4.2.0 dokumentációját.

Támogatott platformok

A .NET-ügyfélkódtár bármely .NET Standard 2.0- vagy .NET 6-platformot támogat, beleértve a következőket:

  • .NET MAUI Android, iOS és Windows platformokhoz.
  • Android API 21- és újabb verzió (Xamarin és Android for .NET).
  • iOS 12.0-s és újabb verzió (Xamarin és iOS for .NET).
  • Univerzális Windows-platform 19041-ben és újabb verziókban.
  • Windows-bemutató keretrendszer (WPF).
  • Windows-alkalmazás SDK (WinUI 3).
  • Xamarin.Forms

Emellett mintákat hoztak létre az Avalonia és a Uno Platform számára. A TodoApp-minta minden tesztelt platformra tartalmaz példát.

Beállítás és előfeltételek

Adja hozzá a következő kódtárakat a NuGetből:

Platformprojekt (például .NET MAUI) használata esetén győződjön meg arról, hogy hozzáadja a kódtárakat a platformprojekthez és a megosztott projektekhez.

A szolgáltatásügyfél létrehozása

Az alábbi kód létrehozza a szolgáltatásügyfélt, amely a háttér- és offline táblákhoz való kommunikáció koordinálására szolgál.

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

Az előző kódban cserélje le MOBILE_APP_URL a ASP.NET Core háttérrendszer URL-címét. Az ügyfelet egyetlentonként kell létrehozni. Hitelesítésszolgáltató használata esetén az alábbi módon konfigurálható:

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

A hitelesítésszolgáltatóval kapcsolatos további részleteket a dokumentum későbbi részében találja.

Beállítások

A beállítások teljes (alapértelmezett) készlete az alábbi módon hozható létre:

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

A HTTP-kérés általában úgy történik, hogy a kérést átadja a hitelesítésszolgáltatónak (amely hozzáadja az Authorization aktuálisan hitelesített felhasználó fejlécét) a kérés elküldése előtt. Igény szerint további delegáló kezelőket is hozzáadhat. Minden kérés átmegy a delegáló kezelőkön, mielőtt elküldené őket a szolgáltatásnak. A kezelők delegálásával további fejléceket adhat hozzá, újrapróbálkozhat, vagy naplózási képességeket biztosíthat.

A cikk későbbi részében példákat talál a delegálási kezelőkre a naplózáshoz és a kérésfejlécek hozzáadásához.

IdGenerator

Amikor egy entitást hozzáad egy offline táblához, annak azonosítóval kell rendelkeznie. A rendszer létrehoz egy azonosítót, ha nincs megadva. Ezzel IdGenerator a beállítással testre szabhatja a létrehozott azonosítót. Alapértelmezés szerint globálisan egyedi azonosító jön létre. A következő beállítás például létrehoz egy sztringet, amely tartalmazza a tábla nevét és a GUID azonosítót:

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

InstallationId

Ha egy InstallationId beállítás be van állítva, a rendszer minden kéréssel egy egyéni fejlécet X-ZUMO-INSTALLATION-ID küld, amely azonosítja az alkalmazás kombinációját egy adott eszközön. Ez a fejléc rögzíthető naplókban, és lehetővé teszi az alkalmazás különböző telepítéseinek számának meghatározását. Ha használja InstallationId, az azonosítót állandó tárolóban kell tárolni az eszközön, hogy nyomon lehessen követni az egyedi telepítéseket.

OfflineStore

Ez OfflineStore az offline adathozzáférés konfigurálásához használatos. További információ: Kapcsolat nélküli táblákkal végzett munka.

ParallelOperations

Az offline szinkronizálási folyamat része az üzenetsoros műveletek távoli kiszolgálóra való leküldése. A leküldéses művelet aktiválásakor a rendszer a műveletet a kapott sorrendben küldi el. Igény szerint akár nyolc szálat is használhat ezeknek a műveleteknek a leküldéséhez. A párhuzamos műveletek több erőforrást használnak az ügyfélen és a kiszolgálón is a művelet gyorsabb elvégzéséhez. Több szál használata esetén nem garantálható, hogy a műveletek milyen sorrendben érkezzenek a kiszolgálóra.

Szerializáló Gépház

Ha módosította a szerializáló beállításait az adatszinkronizálási kiszolgálón, ugyanazokat a módosításokat kell végrehajtania az SerializerSettings ügyfélen. Ezzel a beállítással saját szerializáló-beállításokat adhat meg.

TableEndpointResolver

Konvenció szerint a táblák a távoli szolgáltatásban találhatók az /tables/{tableName} elérési úton (a Route kiszolgálókód attribútuma által meghatározottak szerint). A táblák azonban bármely végponti útvonalon létezhetnek. Ez TableEndpointResolver egy függvény, amely egy táblanevet a távoli szolgáltatással való kommunikáció útvonalává alakít át.

A következő például úgy módosítja a feltételezést, hogy az összes tábla a következő alatt /apitalálható:

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

Useragent

Az adatszinkronizálási ügyfél létrehoz egy megfelelő user-agent fejlécértéket a kódtár verziója alapján. Egyes fejlesztők úgy érzik, hogy a felhasználói ügynök fejléce kiszivárog az ügyfélről. A tulajdonságot UserAgent bármely érvényes fejlécértékre beállíthatja.

Távoli táblákkal végzett munka

Az alábbi szakasz bemutatja, hogyan kereshet és kérdezhet le rekordokat, és hogyan módosíthatja az adatokat egy távoli táblában. Az alábbi témaköröket ismertetik:

Távoli táblahivatkozás létrehozása

Távoli táblahivatkozás létrehozásához használja a következőt GetRemoteTable<T>:

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

Ha írásvédett táblát szeretne visszaadni, használja a következő verziót IReadOnlyRemoteTable<T> :

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

A modelltípusnak a szolgáltatásból kell megvalósítania ITableData a szerződést. A szükséges mezők megadására használható DatasyncClientData :

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

Az DatasyncClientData objektum a következőket tartalmazza:

  • Id (karakterlánc) – az elem globálisan egyedi azonosítója.
  • UpdatedAt (System.DataTimeOffset) – az elem legutóbbi frissítésének dátuma/időpontja.
  • Version (karakterlánc) – a verziószámozáshoz használt átlátszatlan sztring.
  • Deleted (logikai) – ha true, az elem törlődik.

A szolgáltatás ezeket a mezőket kezeli. Ne módosítsa ezeket a mezőket az ügyfélalkalmazás részeként.

A modellek Newtonsoft.JSON attribútumokkal jegyzetelhetők. A tábla neve az attribútum használatával DataTable adható meg:

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

Másik lehetőségként adja meg a tábla nevét a GetRemoteTable() hívásban:

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

Az ügyfél az elérési utat /tables/{tablename} használja URI-ként. A tábla neve az SQLite-adatbázisban található offline tábla neve is.

Támogatott típusok

A primitív típusok (int, float, sztring stb.) mellett a modellek esetében a következő típusok támogatottak:

  • System.DateTime - ISO-8601 UTC dátum/idő sztringként ms pontosságú.
  • System.DateTimeOffset - ISO-8601 UTC dátum/idő sztringként ms pontosságú.
  • System.Guid - kötőjelként elválasztott 32 számjegyként formázva.

Adatok lekérdezése távoli kiszolgálóról

A távoli tábla LINQ-szerű utasításokkal használható, beleértve a következőket:

  • Szűrés záradékkal .Where() .
  • Rendezés különböző .OrderBy() záradékokkal.
  • Tulajdonságok kijelölése a beállítással .Select()
  • Lapozás és .Skip().Take().

Elemek megszámlálása lekérdezésből

Ha szüksége van a lekérdezés által visszaadott elemek számára, használhatja .CountItemsAsync() egy táblában vagy .LongCountAsync() egy lekérdezésben:

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

Ez a módszer a kiszolgálóra való oda-vissza utazást okoz. A lista feltöltése közben is számíthat (például), így elkerülheti az extra oda-vissza utat:

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

A szám a tábla tartalmának lekérésére irányuló első kérés után lesz feltöltve.

Az összes adat visszaadása

Az adatok egy IAsyncEnumerable használatával lesznek visszaadva:

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

A következő megszüntetési záradékok bármelyikével konvertálja a IAsyncEnumerable<T> gyűjteményt egy másik gyűjteményre:

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

A háttérben a távoli táblázat kezeli az eredmény lapozását. A rendszer minden elemet visszaad, függetlenül attól, hogy hány kiszolgálóoldali kérésre van szükség a lekérdezés teljesítéséhez. Ezek az elemek a lekérdezési eredményekben is elérhetők (például remoteTable.Where(m => m.Rating == "R")).

Az adatszinkronizálási keretrendszer egy szálbiztos megfigyelhető gyűjteményt is biztosít ConcurrentObservableCollection<T> . Ez az osztály olyan felhasználói felületi alkalmazások környezetében használható, amelyek általában egy lista (például Xamarin-űrlapok vagy MAUI-listák) kezelésére szolgálnak ObservableCollection<T> . Közvetlenül egy táblából vagy lekérdezésből törölheti és betöltheti ConcurrentObservableCollection<T> a következő adatokat:

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

Az .ToObservableCollection(collection) eseményt az CollectionChanged egyes elemek helyett csak egyszer aktiválja a teljes gyűjteményben, ami gyorsabb újraírási időt eredményez.

A ConcurrentObservableCollection<T> predikátumalapú módosításokkal is rendelkezik:

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

A predikátumalapú módosítások akkor használhatók az eseménykezelőkben, ha az elem indexe nem ismert előre.

Az adatok szűrése

Egy záradék használatával .Where() szűrheti az adatokat. Például:

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

A szűrés az IAsyncEnumerable előtti szolgáltatáson és az ügyfélen történik az IAsyncEnumerable után. Például:

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

Az első .Where() záradék (csak hiányos elemek visszaadása) a szolgáltatáson lesz végrehajtva, míg a második .Where() záradék (a "The" kezdettől kezdve) az ügyfélen lesz végrehajtva.

A Where záradék támogatja az OData-részhalmazba lefordított műveleteket. A műveletek a következők:

  • Relációs operátorok (==, !=, <, <=, >), >=
  • Számtani operátorok (+, -, /, *, ), %
  • Szám pontossága (Math.Floor, Math.Ceiling),
  • Sztringfüggvények (Length, Substring, Replace, IndexOf, Equals, StartsWith) EndsWith(csak ordinális és invariáns kultúrák),
  • Dátumtulajdonságok (Year, Month, Day, Hour, Minute), Second
  • Objektum hozzáférési tulajdonságai és
  • A műveletek bármelyikét kombináló kifejezések.

Adatok rendezése

Az adatok rendezéséhez használja .OrderBy()a , .OrderByDescending(), .ThenBy()és .ThenByDescending() egy tulajdonságkiegészítőt.

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

A rendezést a szolgáltatás végzi. A rendezési záradékban nem adhat meg kifejezést. Ha kifejezés szerint szeretne rendezni, használjon ügyféloldali rendezést:

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

Tulajdonságok kiválasztása

A szolgáltatásból az adatok egy részhalmazát is visszaadhatja:

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

Adatoldal visszaadása

Az adatkészlet egy részhalmazát a lapozás használatával és .Take() implementálásával .Skip() is visszaadhatja:

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

Egy valós alkalmazásban az előző példához hasonló lekérdezéseket használhat lapozóvezérlővel vagy hasonló felhasználói felülettel a lapok közötti navigáláshoz.

Az eddig ismertetett összes függvény additív, így tovább láncolhatjuk őket. Minden láncolt hívás több lekérdezésre is hatással van. Még egy példa:

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

Távoli adatok keresése azonosító alapján

A GetItemAsync függvény egy adott azonosítóval rendelkező objektumok keresésére használható az adatbázisból.

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

Ha a lekérni kívánt elem helyreállíthatóan törölve lett, a paramétert includeDeleted kell használnia:

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

Adatok beszúrása a távoli kiszolgálón

Minden ügyféltípusnak tartalmaznia kell egy Azonosító nevű tagot, amely alapértelmezés szerint egy sztring. Ez az azonosító szükséges a CRUD-műveletek végrehajtásához és az offline szinkronizáláshoz. Az alábbi kód bemutatja, hogyan szúrhat be új sorokat a táblázatba a InsertItemAsync metódussal. A paraméter tartalmazza a .NET-objektumként beszúrni kívánt adatokat.

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

Ha egy egyedi egyéni azonosító nem szerepel a item beszúrás során, a kiszolgáló létrehoz egy azonosítót. A generált azonosító lekéréséhez vizsgálja meg az objektumot a hívás visszatérése után.

Adatok frissítése a távoli kiszolgálón

Az alábbi kód bemutatja, hogyan lehet a ReplaceItemAsync metódussal frissíteni egy meglévő rekordot ugyanazzal az azonosítóval új információkkal.

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

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

Adatok törlése a távoli kiszolgálón

Az alábbi kód bemutatja, hogyan használható a DeleteItemAsync metódus egy meglévő példány törlésére.

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

await todoTable.DeleteItemAsync(item);

Ütközésfeloldás és optimista egyidejűség

Két vagy több ügyfél egyszerre írhat módosításokat ugyanarra az elemre. Ütközésészlelés nélkül az utolsó írás felülírja a korábbi frissítéseket. Az optimista egyidejűség-vezérlés feltételezi, hogy minden tranzakció véglegesítésre képes, ezért nem használ erőforrás-zárolást. Az optimista egyidejűség-vezérlés ellenőrzi, hogy más tranzakció nem módosította-e az adatokat az adatok véglegesítése előtt. Ha az adatok módosultak, a tranzakció vissza lesz állítva.

Az Azure Mobile Apps támogatja az optimista egyidejűség-vezérlést azáltal, hogy nyomon követi az egyes elemek módosításait a version Mobile App háttérrendszer minden táblájához meghatározott rendszertulajdonság-oszlop használatával. Minden alkalommal, amikor egy rekord frissül, a Mobile Apps új értékre állítja a version rekord tulajdonságát. Minden frissítési kérelem során a version kérelemben szereplő rekord tulajdonsága a kiszolgálón lévő rekord ugyanazon tulajdonságával lesz összehasonlítva. Ha a kéréssel átadott verzió nem egyezik a háttérrendszerrel, akkor az ügyfélkódtár kivételt DatasyncConflictException<T> emel ki. A kivételhez tartozó típus a rekord kiszolgálói verzióját tartalmazó háttérrendszer rekordja. Az alkalmazás ezt az információt felhasználhatja annak eldöntésére, hogy végrehajtja-e újra a frissítési kérést a háttérrendszer megfelelő version értékével a módosítások véglegesítéséhez.

Az optimista egyidejűség automatikusan engedélyezve van az DatasyncClientData alapobjektum használatakor.

Az optimista egyidejűség engedélyezése mellett a kód kivételét DatasyncConflictException<T> is el kell kapnia. Az ütközés feloldásához alkalmazza a megfelelőt version a frissített rekordra, majd ismételje meg a hívást a feloldott rekorddal. Az alábbi kód bemutatja, hogyan oldhat fel írási ütközést az észlelés után:

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

Offline táblákkal végzett munka

Az offline táblák helyi SQLite-tárolót használnak az adatok offline állapotban való tárolásához. Minden táblaművelet a távoli kiszolgálótároló helyett a helyi SQLite-tárolón történik. Győződjön meg arról, hogy minden Microsoft.Datasync.Client.SQLiteStore platformprojekthez és minden megosztott projekthez hozzáadja azokat.

A táblahivatkozás létrehozása előtt a helyi tárolót fel kell készíteni:

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

Az áruház definiálása után létrehozhatja az ügyfelet:

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

Végül meg kell győződnie arról, hogy az offline képességek inicializálva vannak:

await client.InitializeOfflineStoreAsync();

Az áruház inicializálása általában közvetlenül az ügyfél létrehozása után történik. Az Offline Csatlakozás ionString az SQLite-adatbázis helyének és az adatbázis megnyitásának beállításainak megadására szolgáló URI. További információ: URI-fájlnevek az SQLite-ben.

  • Memóriabeli gyorsítótár használatához használja file:inmemory.db?mode=memory&cache=privatea .
  • Fájl használatához használja a file:/path/to/file.db

Meg kell adnia a fájl abszolút fájlnevét. A Xamarin használata esetén a Xamarin Essentials fájlrendszer-segítőivel létrehozhat egy elérési utat: Például:

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

Ha MAUI-t használ, a MAUI fájlrendszer-segítőivel létrehozhat egy útvonalat: Például:

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

Offline tábla létrehozása

A táblázathivatkozás a GetOfflineTable<T> következő módszerrel kérhető le:

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

A távoli táblához hasonlóan egy írásvédett offline táblát is közzétehet:

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

Offline tábla használatához nem kell hitelesítést végeznie. Csak akkor kell hitelesítenie magát, ha a háttérszolgáltatással kommunikál.

Offline tábla szinkronizálása

Az offline táblák alapértelmezés szerint nem szinkronizálódnak a háttérrendszerrel. A szinkronizálás két részre van osztva. A módosításokat külön is leküldheti az új elemek letöltésétől. Például:

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

Alapértelmezés szerint minden tábla növekményes szinkronizálást használ – csak az új rekordok lesznek lekérve. Minden egyes egyedi lekérdezéshez rekord tartozik (az OData-lekérdezés MD5 kivonatának létrehozásával jön létre).

Megjegyzés:

Az első argumentum az PullItemsAsync OData-lekérdezés, amely jelzi, hogy mely rekordokat kell lekérni az eszközre. Jobb, ha úgy módosítja a szolgáltatást, hogy csak a felhasználóra jellemző rekordokat adja vissza, nem pedig összetett lekérdezéseket hoz létre az ügyféloldalon.

Az (objektum PullOptions által definiált) beállításokat általában nem kell megadni. A lehetőségek a következők:

  • PushOtherTables - ha igaz értékre van állítva, a rendszer minden táblát leküld.
  • QueryId - egy adott lekérdezésazonosító, amelyet a létrehozott helyett használni kell.
  • WriteDeltaTokenInterval - milyen gyakran kell írni a növekményes szinkronizálás nyomon követéséhez használt delta-tokent.

Az SDK implicit PushAsync() módon hajtja végre a rekordokat.

Ütközéskezelés történik egy PullAsync() metóduson. Az ütközések kezelése ugyanúgy, mint az online táblák. Az ütközés akkor jön létre, amikor PullAsync() a rendszer a beszúrás, frissítés vagy törlés helyett meghívja őket. Ha több ütközés történik, azokat egyetlen PushFailedExceptioncsomagba csomagolja. Minden hibát külön kezeljen.

Változások leküldése az összes táblához

Ha az összes módosítást le szeretné küldeni a távoli kiszolgálóra, használja a következőt:

await client.PushTablesAsync();

A táblázatok egy részhalmazának módosításainak leküldéséhez adja meg a PushTablesAsync() metódustIEnumerable<string>:

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

client.PendingOperations A tulajdonság használatával olvassa be a távoli szolgáltatásba leküldésre váró műveletek számát. Ez a tulajdonság akkor van null , ha nincs offline tároló konfigurálva.

Összetett SQLite-lekérdezések futtatása

Ha összetett SQL-lekérdezéseket kell végrehajtania az offline adatbázison, ezt a ExecuteQueryAsync() módszerrel teheti meg. Például egy SQL JOIN utasításhoz definiáljon egy JObject olyan értéket, amely a visszatérési érték szerkezetét mutatja, majd használja a következőt 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.

A definíció kulcsok/értékek halmaza. A kulcsoknak meg kell egyeznie az SQL-lekérdezés által visszaadott mezőnevekkel, és az értékeknek a várt típus alapértelmezett értékének kell lenniük. Számokhoz 0L (hosszú), false logikai értékekhez és string.Empty minden máshoz használható.

Az SQLite támogatott típusok korlátozó készletével rendelkezik. A dátum/időpontok az összehasonlítás engedélyezéséhez a korszak óta eltelt ezredmásodpercek számaként vannak tárolva.

Felhasználók hitelesítése

Az Azure Mobile Apps lehetővé teszi, hogy hitelesítési szolgáltatót hozzon létre a hitelesítési hívások kezeléséhez. Adja meg a hitelesítési szolgáltatót a szolgáltatásügyfél létrehozásakor:

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

Amikor hitelesítésre van szükség, a rendszer meghívja a hitelesítésszolgáltatót a jogkivonat lekérésére. Általános hitelesítési szolgáltató használható mind az engedélyezési fejlécalapú hitelesítéshez, mind az App Service-hitelesítéshez és az engedélyezésen alapuló hitelesítéshez. Használja a következő modellt:

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 */"
    };
}

A hitelesítési jogkivonatok gyorsítótárazva vannak a memóriában (soha nem íródnak az eszközre), és szükség esetén frissülnek.

A Microsoft Identitásplatform használata

A Microsoft Identitásplatform lehetővé teszi, hogy egyszerűen integrálható legyen a Microsoft Entra-azonosítóval. A Microsoft Entra-hitelesítés implementálásával kapcsolatos teljes oktatóanyagért tekintse meg a gyors üzembe helyezési oktatóanyagokat. Az alábbi kód egy példa a hozzáférési jogkivonat beolvasására:

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

A Microsoft Identitásplatform ASP.NET 6-os verziójával való integrálásáról a Microsoft Identitásplatform dokumentációjában talál további információt.

A Xamarin Essentials vagy a MAUI WebAuthenticator használata

A Azure-alkalmazás szolgáltatáshitelesítéshez a Xamarin Essentials WebAuthenticator vagy a MAUI WebAuthenticator használatával szerezhet be jogkivonatot:

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

DisplayName Az UserId Azure-alkalmazás szolgáltatáshitelesítés használatakor nem érhető el közvetlenül. Ehelyett használjon lusta kérelmezőt az információk lekéréséhez a /.auth/me végpontról:

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

Speciális témakörök

Entitások törlése a helyi adatbázisban

Normál művelet esetén nem szükséges kiüríteni az entitásokat. A szinkronizálási folyamat eltávolítja a törölt entitásokat, és fenntartja a helyi adatbázistáblákhoz szükséges metaadatokat. Vannak azonban olyan esetek, amikor az adatbázison belüli entitások törlése hasznos. Ilyen eset például, ha nagy számú entitást kell törölnie, és hatékonyabb az adatok helyi törlése a táblából.

Ha rekordokat szeretne kiüríteni egy táblából, használja a következőt table.PurgeItemsAsync():

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

A lekérdezés azonosítja a táblából eltávolítandó entitásokat. Azonosítsa a LINQ használatával kiürítendő entitásokat:

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

Az PurgeOptions osztály beállításokat biztosít a törlési művelet módosításához:

  • DiscardPendingOperations elvet minden függőben lévő műveletet a kiszolgálónak küldendő műveleti üzenetsorban lévő táblához.
  • QueryId egy lekérdezésazonosítót ad meg, amely a művelethez használandó delta-jogkivonat azonosítására szolgál.
  • TimestampUpdatePolicy a törlési művelet végén a delta-jogkivonat beállítását adja meg:
    • TimestampUpdatePolicy.NoUpdate azt jelzi, hogy a delta-jogkivonatot nem szabad frissíteni.
    • TimestampUpdatePolicy.UpdateToLastEntity azt jelzi, hogy a változási jogkivonatot frissíteni kell a updatedAt táblában tárolt utolsó entitás mezőjére.
    • TimestampUpdatePolicy.UpdateToNow azt jelzi, hogy a változási jogkivonatot az aktuális dátumra/időpontra kell frissíteni.
    • TimestampUpdatePolicy.UpdateToEpoch azt jelzi, hogy a változási jogkivonatot alaphelyzetbe kell állítani az összes adat szinkronizálásához.

Az adatok szinkronizálásához használja ugyanazt QueryId az értéket, amelyet a híváskor table.PullItemsAsync() használt. A QueryId törlés befejezésekor frissíteni kívánt delta-jogkivonatot adja meg.

Kérelemfejlécek testreszabása

Előfordulhat, hogy az adott alkalmazásforgatókönyv támogatásához testre kell szabnia a mobilalkalmazás háttérrendszerével folytatott kommunikációt. Hozzáadhat például egy egyéni fejlécet minden kimenő kéréshez, vagy módosíthatja a válaszállapot-kódokat, mielőtt visszatér a felhasználóhoz. Használjon egyéni DelegatingHandlert, ahogyan az alábbi példában is látható:

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

Kérésnaplózás engedélyezése

DelegatingHandlerrel is hozzáadhat kérésnaplózást:

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

Szinkronizálási események monitorozása

Szinkronizálási esemény esetén az esemény közzé lesz téve az client.SynchronizationProgress esemény delegáltja számára. Az események a szinkronizálási folyamat előrehaladásának figyelésére használhatók. A szinkronizálási eseménykezelőt az alábbiak szerint határozhatja meg:

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

A SynchronizationEventArgs típus a következőképpen van definiálva:

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

A benne lévő args tulajdonságok vagy null akkor vannak, vagy -1 ha a tulajdonság nem releváns a szinkronizálási esemény szempontjából.