Så här använder du Azure Mobile Apps-klientbiblioteket för .NET

Den här guiden visar hur du utför vanliga scenarier med hjälp av .NET-klientbiblioteket för Azure Mobile Apps. Använd .NET-klientbiblioteket i alla .NET 6- eller .NET Standard 2.0-program, inklusive MAUI, Xamarin och Windows (WPF, UWP och WinUI).

Om du inte har använt Azure Mobile Apps tidigare kan du överväga att först slutföra en av snabbstartsguiderna:

Kommentar

Den här artikeln beskriver den senaste versionen (v6.0) av Microsoft Datasync Framework. Information om äldre klienter finns i v4.2.0-dokumentationen.

Plattformar som stöds

.NET-klientbiblioteket stöder alla .NET Standard 2.0- eller .NET 6-plattformar, inklusive:

  • .NET MAUI för Android-, iOS- och Windows-plattformar.
  • Android API-nivå 21 och senare (Xamarin och Android för .NET).
  • iOS version 12.0 och senare (Xamarin och iOS för .NET).
  • Universell Windows-plattform bygger 19041 och senare.
  • Windows Presentation Framework (WPF).
  • SDK för Windows-appar (WinUI 3).
  • Xamarin.Forms

Dessutom har prover skapats för Avalonia och Uno Platform. TodoApp-exemplet innehåller ett exempel på varje testad plattform.

Installation och förutsättningar

Lägg till följande bibliotek från NuGet:

Om du använder ett plattformsprojekt (till exempel .NET MAUI) ska du lägga till biblioteken i plattformsprojektet och alla delade projekt.

Skapa tjänstklienten

Följande kod skapar tjänstklienten, som används för att samordna all kommunikation till serverdels- och offlinetabellerna.

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

I föregående kod ersätter du MOBILE_APP_URL med URL:en för ASP.NET Core-serverdelen. Klienten ska skapas som en singleton. Om du använder en autentiseringsprovider kan den konfigureras så här:

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

Mer information om autentiseringsprovidern finns senare i det här dokumentet.

Alternativ

En fullständig (standard) uppsättning alternativ kan skapas så här:

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

Normalt görs en HTTP-begäran genom att skicka begäran via autentiseringsprovidern (som lägger till Authorization huvudet för den för närvarande autentiserade användaren) innan begäran skickas. Du kan också lägga till fler delegeringshanterare. Varje begäran skickas via delegeringshanterare innan den skickas till tjänsten. Om du delegerar hanterare kan du lägga till extra huvuden, göra återförsök eller tillhandahålla loggningsfunktioner.

Exempel på delegerande hanterare finns för loggning och tillägg av begärandehuvuden senare i den här artikeln.

IdGenerator

När en entitet läggs till i en offlinetabell måste den ha ett ID. Ett ID genereras om ett inte anges. Med IdGenerator alternativet kan du skräddarsy det ID som genereras. Som standard genereras ett globalt unikt ID. Följande inställning genererar till exempel en sträng som innehåller tabellnamnet och ett GUID:

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

InstallationId

Om en InstallationId har angetts skickas en anpassad rubrik X-ZUMO-INSTALLATION-ID med varje begäran för att identifiera kombinationen av programmet på en specifik enhet. Det här huvudet kan registreras i loggar och gör att du kan fastställa antalet distinkta installationer för din app. Om du använder InstallationIdska ID:t lagras i beständig lagring på enheten så att unika installationer kan spåras.

OfflineArkiv

OfflineStore Används när du konfigurerar dataåtkomst offline. Mer information finns i Arbeta med offlinetabeller.

ParallelOperations

En del av offlinesynkroniseringsprocessen omfattar push-överföring av köade åtgärder till fjärrservern. När push-åtgärden utlöses skickas åtgärderna i den ordning de togs emot. Du kan också använda upp till åtta trådar för att push-överföra dessa åtgärder. Parallella åtgärder använder fler resurser på både klient och server för att slutföra åtgärden snabbare. Det går inte att garantera i vilken ordning åtgärderna kommer till servern när du använder flera trådar.

Serialiserare Inställningar

Om du har ändrat serialiserarinställningarna på datasynkroniseringsservern måste du göra samma ändringar i SerializerSettings klienten. Med det här alternativet kan du ange egna serialiseringsinställningar.

TableEndpointResolver

Enligt konventionen finns tabeller på fjärrtjänsten på /tables/{tableName} sökvägen (enligt Route attributet i serverkoden). Tabeller kan dock finnas på valfri slutpunktssökväg. TableEndpointResolver är en funktion som omvandlar ett tabellnamn till en sökväg för kommunikation med fjärrtjänsten.

Följande ändrar till exempel antagandet så att alla tabeller finns under /api:

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

UserAgent

Datasynkroniseringsklienten genererar ett lämpligt användaragenthuvudvärde baserat på bibliotekets version. Vissa utvecklare anser att användarens agenthuvud läcker information om klienten. Du kan ange UserAgent egenskapen till valfritt giltigt rubrikvärde.

Arbeta med fjärrtabeller

I följande avsnitt beskrivs hur du söker efter och hämtar poster och ändrar data i en fjärrtabell. Följande avsnitt beskrivs:

Skapa en fjärrtabellreferens

Om du vill skapa en fjärrtabellreferens använder du GetRemoteTable<T>:

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

Om du vill returnera en skrivskyddad tabell använder du versionen IReadOnlyRemoteTable<T> :

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

Modelltypen måste implementera ITableData kontraktet från tjänsten. Använd DatasyncClientData för att ange de obligatoriska fälten:

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

Objektet DatasyncClientData innehåller:

  • Id (sträng) – ett globalt unikt ID för objektet.
  • UpdatedAt (System.DataTimeOffset) – datum/tid då objektet senast uppdaterades.
  • Version (sträng) – en ogenomskinlig sträng som används för versionshantering.
  • Deleted (booleskt) – om truetas objektet bort.

Tjänsten underhåller dessa fält. Justera inte dessa fält som en del av klientprogrammet.

Modeller kan kommenteras med newtonsoft.JSON-attribut. Namnet på tabellen kan anges med hjälp av attributet DataTable :

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

Du kan också ange namnet på tabellen i anropet GetRemoteTable() :

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

Klienten använder sökvägen /tables/{tablename} som URI. Tabellnamnet är också namnet på offlinetabellen i SQLite-databasen.

Typer som stöds

Förutom primitiva typer (int, float, string osv.) stöds följande typer för modeller:

  • System.DateTime – som en ISO-8601 UTC-datum-/tidssträng med ms-noggrannhet.
  • System.DateTimeOffset – som en ISO-8601 UTC-datum-/tidssträng med ms-noggrannhet.
  • System.Guid – formaterad som 32 siffror avgränsade som bindestreck.

Fråga efter data från en fjärrserver

Fjärrtabellen kan användas med LINQ-liknande instruktioner, inklusive:

  • Filtrering med en .Where() sats.
  • Sortering med olika .OrderBy() satser.
  • Välja egenskaper med .Select().
  • Växling med .Skip() och .Take().

Räkna objekt från en fråga

Om du behöver ett antal objekt som frågan skulle returnera kan du använda .CountItemsAsync() i en tabell eller .LongCountAsync() på en fråga:

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

Den här metoden orsakar en tur-och-retur-resa till servern. Du kan också få ett antal när du fyller i en lista (till exempel) och undvika extra tur och retur:

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

Antalet fylls i efter den första begäran om att hämta tabellinnehållet.

Returnerar alla data

Data returneras via en IAsyncEnumerable:

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

Använd någon av följande avslutande satser för att konvertera IAsyncEnumerable<T> till en annan samling:

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

I bakgrunden hanterar fjärrtabellen växling av resultatet åt dig. Alla objekt returneras oavsett hur många begäranden på serversidan som krävs för att uppfylla frågan. Dessa element är också tillgängliga för frågeresultat (till exempel remoteTable.Where(m => m.Rating == "R")).

Ramverket för datasynkronisering tillhandahåller ConcurrentObservableCollection<T> också – en trådsäker observerbar samling. Den här klassen kan användas i kontexten för UI-program som normalt används ObservableCollection<T> för att hantera en lista (till exempel Xamarin Forms eller MAUI-listor). Du kan rensa och läsa in en ConcurrentObservableCollection<T> direkt från en tabell eller fråga:

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

Med utlösare .ToObservableCollection(collection)CollectionChanged utlöses händelsen en gång för hela samlingen i stället för för enskilda objekt, vilket resulterar i en snabbare omsågningstid.

Har ConcurrentObservableCollection<T> också predikatdrivna ändringar:

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

Predikatdrivna ändringar kan användas i händelsehanterare när objektets index inte är känt i förväg.

Filtrera data

Du kan använda en .Where() sats för att filtrera data. Till exempel:

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

Filtrering görs på tjänsten före IAsyncEnumerable och på klienten efter IAsyncEnumerable. Till exempel:

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

Den första .Where() satsen (returnerar endast ofullständiga objekt) körs på tjänsten, medan den andra .Where() satsen (från och med "The") körs på klienten.

Satsen Where stöder åtgärder som översätts till OData-delmängden. Åtgärderna omfattar:

  • Relationsoperatorer (==, !=, <, <=, >, ), >=
  • Aritmetiska operatorer (+, -, /, *, %),
  • Talprecision (Math.Floor, Math.Ceiling),
  • Strängfunktioner (Length, , ReplaceSubstring, IndexOf, Equals, StartsWith, ) EndsWith(endast ordningstal och invarianta kulturer),
  • Datumegenskaper (Year, Month, Day, Hour, Minute, ), Second
  • Åtkomstegenskaper för ett objekt och
  • Uttryck som kombinerar någon av dessa åtgärder.

Sortera data

Använd .OrderBy(), .OrderByDescending(), .ThenBy()och .ThenByDescending() med en egenskapsåtkomst för att sortera data.

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

Sortering görs av tjänsten. Du kan inte ange ett uttryck i någon sorteringssats. Om du vill sortera efter ett uttryck använder du sortering på klientsidan:

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

Välja egenskaper

Du kan returnera en delmängd data från tjänsten:

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

Returnera en sida med data

Du kan returnera en delmängd av datamängden med hjälp av .Skip() och .Take() implementera växling:

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

I en verklig app kan du använda frågor som liknar föregående exempel med en sidkontroll eller ett jämförbart användargränssnitt för att navigera mellan sidor.

Alla funktioner som beskrivs hittills är additiva, så vi kan fortsätta att länka dem. Varje länkat anrop påverkar mer av frågan. Ett exempel till:

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

Leta upp fjärrdata efter ID

Funktionen GetItemAsync kan användas för att söka efter objekt från databasen med ett visst ID.

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

Om objektet som du försöker hämta har tagits bort mjukt måste du använda parametern includeDeleted :

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

Infoga data på fjärrservern

Alla klienttyper måste innehålla en medlem med namnet ID, som som standard är en sträng. Detta ID krävs för att utföra CRUD-åtgärder och för offlinesynkronisering. Följande kod visar hur du använder InsertItemAsync metoden för att infoga nya rader i en tabell. Parametern innehåller de data som ska infogas som ett .NET-objekt.

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

Om ett unikt anpassat ID-värde inte ingår i item under en infogning genererar servern ett ID. Du kan hämta det genererade ID:t genom att inspektera objektet när anropet har returnerats.

Uppdatera data på fjärrservern

Följande kod visar hur du använder ReplaceItemAsync metoden för att uppdatera en befintlig post med samma ID med ny information.

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

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

Ta bort data på fjärrservern

Följande kod visar hur du använder DeleteItemAsync metoden för att ta bort en befintlig instans.

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

await todoTable.DeleteItemAsync(item);

Konfliktlösning och optimistisk samtidighet

Två eller flera klienter kan skriva ändringar i samma objekt samtidigt. Utan konfliktidentifiering skulle den senaste skrivningen skriva över alla tidigare uppdateringar. Optimistisk samtidighetskontroll förutsätter att varje transaktion kan checka in och därför inte använder någon resurslåsning. Optimistisk samtidighetskontroll verifierar att ingen annan transaktion har ändrat data innan data checkas in. Om data har ändrats återställs transaktionen.

Azure Mobile Apps stöder optimistisk samtidighetskontroll genom att spåra ändringar i varje objekt med hjälp av den version systemegenskapskolumn som definieras för varje tabell i mobilappens serverdel. Varje gång en post uppdateras anger Mobile Apps egenskapen för posten version till ett nytt värde. Under varje uppdateringsbegäran version jämförs egenskapen för posten som ingår i begäran med samma egenskap för posten på servern. Om den version som skickades med begäran inte matchar serverdelen skapar klientbiblioteket ett DatasyncConflictException<T> undantag. Den typ som ingår i undantaget är posten från serverdelen som innehåller serverns version av posten. Programmet kan sedan använda den här informationen för att avgöra om uppdateringsbegäran ska köras igen med rätt version värde från serverdelen för att genomföra ändringar.

Optimistisk samtidighet aktiveras automatiskt när du använder DatasyncClientData basobjektet.

Förutom att aktivera optimistisk samtidighet måste du också fånga undantaget DatasyncConflictException<T> i koden. Lös konflikten genom att tillämpa rätt version på den uppdaterade posten och sedan upprepa anropet med den lösta posten. Följande kod visar hur du löser en skrivkonflikt när den har identifierats:

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

Arbeta med offlinetabeller

Offlinetabeller använder ett lokalt SQLite-arkiv för att lagra data för användning när de är offline. Alla tabellåtgärder utförs mot det lokala SQLite-arkivet i stället för fjärrserverarkivet. Se till att du lägger till i Microsoft.Datasync.Client.SQLiteStore varje plattformsprojekt och i alla delade projekt.

Innan en tabellreferens kan skapas måste det lokala arkivet förberedas:

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

När butiken har definierats kan du skapa klienten:

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

Slutligen måste du se till att offlinefunktionerna initieras:

await client.InitializeOfflineStoreAsync();

Butiksinitiering görs normalt omedelbart efter att klienten har skapats. Offline Anslut ionString är en URI som används för att ange både platsen för SQLite-databasen och de alternativ som används för att öppna databasen. Mer information finns i URI-filnamn i SQLite.

  • Om du vill använda en minnesintern cache använder du file:inmemory.db?mode=memory&cache=private.
  • Om du vill använda en fil använder du file:/path/to/file.db

Du måste ange det absoluta filnamnet för filen. Om du använder Xamarin kan du använda Xamarin Essentials-filsystemhjälparna för att skapa en sökväg: Till exempel:

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

Om du använder MAUI kan du använda MAUI-filsystemhjälparna för att skapa en sökväg: Till exempel:

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

Skapa en offlinetabell

En tabellreferens kan hämtas med hjälp av GetOfflineTable<T> metoden:

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

Precis som med fjärrtabellen kan du också exponera en skrivskyddad offlinetabell:

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

Du behöver inte autentisera för att använda en offlinetabell. Du behöver bara autentisera när du kommunicerar med serverdelstjänsten.

Synkronisera en offlinetabell

Offlinetabeller synkroniseras inte med serverdelen som standard. Synkroniseringen är uppdelad i två delar. Du kan skicka ändringar separat från att ladda ned nya objekt. Till exempel:

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

Som standard använder alla tabeller inkrementell synkronisering – endast nya poster hämtas. En post ingår för varje unik fråga (genereras genom att skapa en MD5-hash för OData-frågan).

Kommentar

Det första argumentet är PullItemsAsync OData-frågan som anger vilka poster som ska hämtas till enheten. Det är bättre att ändra tjänsten så att den endast returnerar poster som är specifika för användaren i stället för att skapa komplexa frågor på klientsidan.

Alternativen (som definieras av PullOptions objektet) behöver vanligtvis inte anges. Alternativen inkluderar:

  • PushOtherTables – om värdet är true skickas alla tabeller.
  • QueryId – ett specifikt fråge-ID som ska användas i stället för det genererade.
  • WriteDeltaTokenInterval – hur ofta deltatoken ska skrivas för att spåra inkrementell synkronisering.

SDK utför en implicit PushAsync() innan du hämtar poster.

Konflikthantering sker på en PullAsync() metod. Hantera konflikter på samma sätt som onlinetabeller. Konflikten skapas när PullAsync() anropas i stället för under infogningen, uppdateringen eller borttagningen. Om flera konflikter inträffar paketeras de till en enda PushFailedException. Hantera varje fel separat.

Push-ändringar för alla tabeller

Om du vill skicka alla ändringar till fjärrservern använder du:

await client.PushTablesAsync();

Om du vill push-överföra ändringar för en delmängd tabeller anger du en IEnumerable<string> till PushTablesAsync() -metoden:

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

Använd egenskapen client.PendingOperations för att läsa antalet åtgärder som väntar på att skickas till fjärrtjänsten. Den här egenskapen är null när inget offlinearkiv har konfigurerats.

Köra komplexa SQLite-frågor

Om du behöver göra komplexa SQL-frågor mot offlinedatabasen kan du göra det med hjälp av ExecuteQueryAsync() metoden . Om du till exempel vill göra en SQL JOIN -instruktion definierar du en JObject som visar strukturen för returvärdet och använder ExecuteQueryAsync()sedan :

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.

Definitionen är en uppsättning nyckel/värden. Nycklarna måste matcha fältnamnen som SQL-frågan returnerar och värdena måste vara standardvärdet för den förväntade typen. Använd 0L för tal (lång), false för booleska värden och string.Empty för allt annat.

SQLite har en restriktiv uppsättning typer som stöds. Datum/tider lagras som antalet millisekunder sedan epoken för att tillåta jämförelser.

Autentisera användare

Med Azure Mobile Apps kan du generera en autentiseringsprovider för hantering av autentiseringsanrop. Ange autentiseringsprovidern när du skapar tjänstklienten:

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

När autentisering krävs anropas autentiseringsprovidern för att hämta token. En allmän autentiseringsprovider kan användas för både auktoriseringshuvudbaserad autentisering och App Service-autentisering och auktoriseringsbaserad autentisering. Använd följande modell:

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

Autentiseringstoken cachelagras i minnet (skrivs aldrig till enheten) och uppdateras vid behov.

Använd Microsofts identitetsplattform

Med Microsofts identitetsplattform kan du enkelt integrera med Microsoft Entra-ID. I snabbstartsguiderna finns en fullständig självstudie om hur du implementerar Microsoft Entra-autentisering. Följande kod visar ett exempel på hur du hämtar åtkomsttoken:

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

Mer information om hur du integrerar Microsofts identitetsplattform med ASP.NET 6 finns i dokumentationen för Microsofts identitetsplattform.

Använda Xamarin Essentials eller MAUI WebAuthenticator

För Azure App Service-autentisering kan du använda Xamarin Essentials WebAuthenticator eller MAUI WebAuthenticator för att hämta en token:

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

Och UserIdDisplayName är inte direkt tillgängliga när du använder Azure App Service-autentisering. Använd i stället en lat beställare för att hämta informationen från /.auth/me slutpunkten:

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

Avancerade ämnen

Rensa entiteter i den lokala databasen

Under normal drift krävs inte rensning av entiteter. Synkroniseringsprocessen tar bort borttagna entiteter och underhåller nödvändiga metadata för lokala databastabeller. Det finns dock tillfällen då rensning av entiteter i databasen är användbart. Ett sådant scenario är när du behöver ta bort ett stort antal entiteter och det är mer effektivt att rensa data från tabellen lokalt.

Om du vill rensa poster från en tabell använder du table.PurgeItemsAsync():

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

Frågan identifierar de entiteter som ska tas bort från tabellen. Identifiera de entiteter som ska rensas med LINQ:

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

Klassen PurgeOptions innehåller inställningar för att ändra rensningsåtgärden:

  • DiscardPendingOperations tar bort väntande åtgärder för tabellen som finns i driftkön och väntar på att skickas till servern.
  • QueryId anger ett fråge-ID som används för att identifiera deltatoken som ska användas för åtgärden.
  • TimestampUpdatePolicy anger hur du justerar deltatoken i slutet av rensningsåtgärden:
    • TimestampUpdatePolicy.NoUpdate anger att deltatoken inte får uppdateras.
    • TimestampUpdatePolicy.UpdateToLastEntity anger att deltatoken ska uppdateras till fältet updatedAt för den senaste entiteten som lagras i tabellen.
    • TimestampUpdatePolicy.UpdateToNow anger att deltatoken ska uppdateras till aktuellt datum/tid.
    • TimestampUpdatePolicy.UpdateToEpoch anger att deltatoken ska återställas för att synkronisera alla data.

Använd samma QueryId värde som du använde när du anropade table.PullItemsAsync() för att synkronisera data. QueryId Anger deltatoken som ska uppdateras när rensningen är klar.

Anpassa begärandehuvuden

För att stödja ditt specifika appscenario kan du behöva anpassa kommunikationen med mobilappens serverdel. Du kan till exempel lägga till en anpassad rubrik i varje utgående begäran eller ändra svarsstatuskoder innan du återgår till användaren. Använd en anpassad DelegeringHandler, som i följande exempel:

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

Aktivera loggning av begäranden

Du kan också använda en DelegeringHandler för att lägga till loggning av begäranden:

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

Övervaka synkroniseringshändelser

När en synkroniseringshändelse inträffar publiceras händelsen till händelsedelegaten client.SynchronizationProgress . Händelserna kan användas för att övervaka synkroniseringsprocessens förlopp. Definiera en synkroniseringshändelsehanterare på följande sätt:

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

Typen SynchronizationEventArgs definieras på följande sätt:

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

Egenskaperna i args är antingen null eller -1 när egenskapen inte är relevant för synkroniseringshändelsen.