Come usare la libreria client di App per dispositivi mobili di Azure v4.2.0 per .NET
Nota
Questo prodotto viene ritirato. Per una sostituzione dei progetti che usano .NET 8 o versione successiva, vedere la libreria datasync di Community Toolkit.
Questa guida illustra come eseguire scenari comuni usando la libreria client .NET per app per dispositivi mobili di Azure. Usare la libreria client .NET nelle applicazioni Windows (WPF, UWP) o Xamarin (Native o Forms). Se non si ha esperienza con app per dispositivi mobili di Azure, è consigliabile completare prima l'esercitazione introduttiva per Xamarin.Forms.
Avvertimento
Questo articolo illustra le informazioni per la versione della libreria v4.2.0, sostituita dalla libreria v5.0.0. Per informazioni più aggiornate, vedere l'articolo relativo alla versione più recente
Piattaforme supportate
La libreria client .NET supporta .NET Standard 2.0 e le piattaforme seguenti:
- Xamarin.Android dal livello API 19 fino al livello API 30.
- Da Xamarin.iOS versione 8.0 a 14.3.
- Universal Windows Platform build 16299 e versioni successive.
- Qualsiasi applicazione .NET Standard 2.0.
L'autenticazione "server-flow" usa un controllo WebView per l'interfaccia utente presentata e potrebbe non essere disponibile in ogni piattaforma. Se non è disponibile, è necessario fornire un'autenticazione "flusso client". Questa libreria client non è adatta ai fattori di forma watch o IoT quando si usa l'autenticazione.
Configurazione e prerequisiti
Si presuppone che l'utente abbia già creato e pubblicato il progetto back-end di App per dispositivi mobili di Azure, che include almeno una tabella. Nel codice usato in questo argomento la tabella è denominata TodoItem
e include una stringa Id
e Text
campi e una colonna Complete
booleana. Questa tabella è la stessa tabella creata al termine dell'avvio rapido .
Il tipo client tipizzato corrispondente in C# è questa classe:
public class TodoItem
{
public string Id { get; set; }
[JsonProperty(PropertyName = "text")]
public string Text { get; set; }
[JsonProperty(PropertyName = "complete")]
public bool Complete { get; set; }
}
Il
Per informazioni su come creare tabelle nel back-end di App per dispositivi mobili, vedere l'argomento .NET Server SDK l'argomento Node.js Server SDK.
Installare il pacchetto SDK client gestito
Fare clic con il pulsante destro del mouse sul progetto, premere Gestisci pacchetti NuGet, cercare il pacchetto di Microsoft.Azure.Mobile.Client
, quindi premere Installa. Per le funzionalità offline, installare anche il pacchetto Microsoft.Azure.Mobile.Client.SQLiteStore
.
Creare il client app per dispositivi mobili di Azure
Il codice seguente crea l'oggetto MobileServiceClient
var client = new MobileServiceClient("MOBILE_APP_URL");
Nel codice precedente sostituire MOBILE_APP_URL
con l'URL del back-end del servizio app. L'oggetto MobileServiceClient
deve essere un singleton.
Usare le tabelle
La sezione seguente illustra in dettaglio come cercare e recuperare i record e modificare i dati all'interno della tabella. Gli argomenti seguenti sono trattati:
- Creare un riferimento a una tabella
- eseguire query sui dati
- Filtro restituito dati
- Ordinare i dati restituiti
- Restituire dati nelle pagine
- Selezionare colonne specifiche
- Cercare un record in base all'ID
- Eseguire query non tipate
- Inserisci dati
- Aggiornare i dati
- Eliminare i dati
- risoluzione dei conflitti e di concorrenza ottimistica
- Associare dati a un'interfaccia utente di Windows
- Modificare le dimensioni della pagina
Creare un riferimento a una tabella
Tutto il codice che accede o modifica i dati in una tabella back-end chiama funzioni sull'oggetto MobileServiceTable
. Ottenere un riferimento alla tabella chiamando il metodo GetTable
IMobileServiceTable<TodoItem> todoTable = client.GetTable<TodoItem>();
L'oggetto restituito utilizza il modello di serializzazione tipizzato. È supportato anche un modello di serializzazione non tipizzato. Nell'esempio seguente crea un riferimento a una tabella non tipizzato:
// Get an untyped table reference
IMobileServiceTable untypedTodoTable = client.GetTable("TodoItem");
Nelle query non tipate è necessario specificare la stringa di query OData sottostante.
Eseguire query sui dati dall'app per dispositivi mobili
Questa sezione descrive come eseguire query al back-end dell'app per dispositivi mobili, che include le funzionalità seguenti:
- Filtro restituito dati
- Ordinare i dati restituiti
- Restituire dati nelle pagine
- Selezionare colonne specifiche
- Cercare un record in base all'ID
Nota
Viene applicata una dimensione di pagina basata su server per impedire la restituzione di tutte le righe. Il paging mantiene le richieste predefinite per set di dati di grandi dimensioni dall'impatto negativo del servizio. Per restituire più di 50 righe, utilizzare il metodo Skip
e Take
, come descritto in Restituire dati nelle pagine.
Filtrare i dati restituiti
Il codice seguente illustra come filtrare i dati includendo una clausola Where
in una query. Restituisce tutti gli elementi di todoTable
la cui proprietà Complete
è uguale a false
. La funzione Where applica un predicato di filtro di riga alla query sulla tabella.
// This query filters out completed TodoItems and items without a timestamp.
List<TodoItem> items = await todoTable
.Where(todoItem => todoItem.Complete == false)
.ToListAsync();
È possibile visualizzare l'URI della richiesta inviata al back-end usando il software di ispezione dei messaggi, ad esempio gli strumenti di sviluppo del browser o Fiddler. Se si esamina l'URI della richiesta, si noti che la stringa di query viene modificata:
GET /tables/todoitem?$filter=(complete+eq+false) HTTP/1.1
Questa richiesta OData viene convertita in una query SQL da Server SDK:
SELECT *
FROM TodoItem
WHERE ISNULL(complete, 0) = 0
La funzione passata al metodo Where
può avere un numero arbitrario di condizioni.
// This query filters out completed TodoItems where Text isn't null
List<TodoItem> items = await todoTable
.Where(todoItem => todoItem.Complete == false && todoItem.Text != null)
.ToListAsync();
Questo esempio viene convertito in una query SQL da Parte di Server SDK:
SELECT *
FROM TodoItem
WHERE ISNULL(complete, 0) = 0
AND ISNULL(text, 0) = 0
Questa query può anche essere suddivisa in più clausole:
List<TodoItem> items = await todoTable
.Where(todoItem => todoItem.Complete == false)
.Where(todoItem => todoItem.Text != null)
.ToListAsync();
I due metodi sono equivalenti e possono essere usati in modo intercambiabile. L'opzione precedente, ovvero la concatenazione di più predicati in una query, è più compatta e consigliata.
La clausola Where
supporta operazioni convertite nel subset OData. Le operazioni includono:
- Operatori relazionali (
==
,!=
,<
,<=
,>
,>=
), - Operatori aritmetici (
+
,-
,/
,*
,%
), - Precisione numerica (
Math.Floor
,Math.Ceiling
), - Funzioni stringa (
Length
,Substring
,Replace
,IndexOf
,StartsWith
,EndsWith
), - Proprietà di data (
Year
,Month
,Day
,Hour
,Minute
,Second
), - Accedere alle proprietà di un oggetto e
- Espressioni che combinano una di queste operazioni.
Quando si valuta il supporto dell'SDK del server, è possibile prendere in considerazione la documentazione di OData v3 .
Ordinare i dati restituiti
Il codice seguente illustra come ordinare i dati includendo una funzione OrderBy o OrderByDescending nella query. Restituisce elementi da todoTable
ordinati in ordine crescente in base al campo Text
.
// Sort items in ascending order by Text field
MobileServiceTableQuery<TodoItem> query = todoTable
.OrderBy(todoItem => todoItem.Text)
List<TodoItem> items = await query.ToListAsync();
// Sort items in descending order by Text field
MobileServiceTableQuery<TodoItem> query = todoTable
.OrderByDescending(todoItem => todoItem.Text)
List<TodoItem> items = await query.ToListAsync();
Restituire i dati nelle pagine
Per impostazione predefinita, il back-end restituisce solo le prime 50 righe. È possibile aumentare il numero di righe restituite chiamando il metodo Take. Usare Take
insieme al metodo Skip per richiedere una specifica "pagina" del set di dati totale restituito dalla query. La query seguente, quando eseguita, restituisce i primi tre elementi della tabella.
// Define a filtered query that returns the top 3 items.
MobileServiceTableQuery<TodoItem> query = todoTable.Take(3);
List<TodoItem> items = await query.ToListAsync();
La query modificata seguente ignora i primi tre risultati e restituisce i tre risultati successivi. Questa query produce la seconda "pagina" di dati, in cui le dimensioni della pagina sono tre elementi.
// Define a filtered query that skips the top 3 items and returns the next 3 items.
MobileServiceTableQuery<TodoItem> query = todoTable.Skip(3).Take(3);
List<TodoItem> items = await query.ToListAsync();
Il metodo IncludeTotalCount richiede il conteggio totale per tutti i i record che sarebbero stati restituiti, ignorando eventuali clausole di paging/limite specificate:
query = query.IncludeTotalCount();
In un'app reale è possibile usare query simili all'esempio precedente con un controllo cercapersone o un'interfaccia utente paragonabile per spostarsi tra le pagine.
Nota
Per eseguire l'override del limite di 50 righe in un back-end dell'app per dispositivi mobili, è necessario applicare anche il EnableQueryAttribute al metodo GET pubblico e specificare il comportamento di paging. Se applicato al metodo , il valore seguente imposta le righe restituite al massimo su 1000:
[EnableQuery(MaxTop=1000)]
Selezionare colonne specifiche
È possibile specificare il set di proprietà da includere nei risultati aggiungendo una clausola Select alla query. Ad esempio, il codice seguente mostra come selezionare un solo campo e come selezionare e formattare più campi:
// Select one field -- just the Text
MobileServiceTableQuery<TodoItem> query = todoTable
.Select(todoItem => todoItem.Text);
List<string> items = await query.ToListAsync();
// Select multiple fields -- both Complete and Text info
MobileServiceTableQuery<TodoItem> query = todoTable
.Select(todoItem => string.Format("{0} -- {1}",
todoItem.Text.PadRight(30), todoItem.Complete ?
"Now complete!" : "Incomplete!"));
List<string> items = await query.ToListAsync();
Tutte le funzioni descritte finora sono additivi, in modo da poterle concatenare. Ogni chiamata concatenato influisce maggiormente sulla query. Un altro esempio:
MobileServiceTableQuery<TodoItem> query = todoTable
.Where(todoItem => todoItem.Complete == false)
.Select(todoItem => todoItem.Text)
.Skip(3).
.Take(3);
List<string> items = await query.ToListAsync();
Cercare i dati in base all'ID
La funzione LookupAsync può essere usata per cercare oggetti dal database con un ID specifico.
// This query filters out the item with the ID of 37BBF396-11F0-4B39-85C8-B319C729AF6D
TodoItem item = await todoTable.LookupAsync("37BBF396-11F0-4B39-85C8-B319C729AF6D");
Eseguire query non tipate
Quando si esegue una query usando un oggetto tabella non tipizzato, è necessario specificare in modo esplicito la stringa di query OData chiamando ReadAsync, come nell'esempio seguente:
// Lookup untyped data using OData
JToken untypedItems = await untypedTodoTable.ReadAsync("$filter=complete eq 0&$orderby=text");
Si ottengono valori JSON che è possibile usare come un contenitore di proprietà. Per altre informazioni su
Inserire dati
Tutti i tipi di client devono contenere un membro denominato ID, che è per impostazione predefinita una stringa. Questo ID è necessario per eseguire operazioni CRUD e per la sincronizzazione offline. Nel codice seguente viene illustrato come utilizzare il metodo insertAsync per inserire nuove righe in una tabella. Il parametro contiene i dati da inserire come oggetto .NET.
await todoTable.InsertAsync(todoItem);
Se un valore ID personalizzato univoco non è incluso nella todoItem
durante un inserimento, viene generato un GUID dal server. È possibile recuperare l'ID generato esaminando l'oggetto dopo la restituzione della chiamata.
Per inserire dati non tipizzato, è possibile sfruttare Json.NET:
JObject jo = new JObject();
jo.Add("Text", "Hello World");
jo.Add("Complete", false);
var inserted = await table.InsertAsync(jo);
Di seguito è riportato un esempio di uso di un indirizzo di posta elettronica come ID stringa univoco:
JObject jo = new JObject();
jo.Add("id", "myemail@emaildomain.com");
jo.Add("Text", "Hello World");
jo.Add("Complete", false);
var inserted = await table.InsertAsync(jo);
Utilizzo dei valori ID
App per dispositivi mobili supporta valori stringa personalizzati univoci per la colonna id
- Gli ID vengono generati senza eseguire un round trip nel database.
- I record sono più facili da unire da tabelle o database diversi.
- I valori degli ID possono integrarsi meglio con la logica di un'applicazione.
Quando un valore ID stringa non è impostato su un record inserito, il back-end dell'app per dispositivi mobili genera un valore univoco per l'ID. È possibile usare il metodo guid.NewGuid
JObject jo = new JObject();
jo.Add("id", Guid.NewGuid().ToString("N"));
Aggiornare i dati
Il codice seguente illustra come usare il metodo updateAsync
await todoTable.UpdateAsync(todoItem);
Per aggiornare i dati non tipizzato, è possibile sfruttare newtonsoft JSON come indicato di seguito:
JObject jo = new JObject();
jo.Add("id", "37BBF396-11F0-4B39-85C8-B319C729AF6D");
jo.Add("Text", "Hello World");
jo.Add("Complete", false);
var inserted = await table.UpdateAsync(jo);
È necessario specificare un campo id
durante l'esecuzione di un aggiornamento. Il back-end usa il campo id
per identificare la riga da aggiornare. Il campo id
può essere ottenuto dal risultato della chiamata InsertAsync
. Se si tenta di aggiornare un elemento senza specificare il valore id
, viene generato un ArgumentException
.
Eliminare i dati
Il codice seguente illustra come usare il metodo DeleteAsync id
impostato nel todoItem
.
await todoTable.DeleteAsync(todoItem);
Per eliminare i dati non tipizzato, è possibile sfruttare Json.NET come indicato di seguito:
JObject jo = new JObject();
jo.Add("id", "37BBF396-11F0-4B39-85C8-B319C729AF6D");
await table.DeleteAsync(jo);
Quando si effettua una richiesta di eliminazione, è necessario specificare un ID. Altre proprietà non vengono passate al servizio o vengono ignorate nel servizio. Il risultato di una chiamata DeleteAsync
è in genere null
. L'ID da passare può essere ottenuto dal risultato della chiamata InsertAsync
. Viene generata una MobileServiceInvalidOperationException
quando si tenta di eliminare un elemento senza specificare il campo id
.
Risoluzione dei conflitti e concorrenza ottimistica
Due o più client possono scrivere modifiche allo stesso elemento contemporaneamente. Senza il rilevamento dei conflitti, l'ultima scrittura sovrascriverà eventuali aggiornamenti precedenti. controllo della concorrenza ottimistica presuppone che ogni transazione possa eseguire il commit e pertanto non usi alcun blocco delle risorse. Prima di eseguire il commit di una transazione, il controllo della concorrenza ottimistica verifica che nessun'altra transazione abbia modificato i dati. Se i dati sono stati modificati, viene eseguito il rollback della transazione di commit.
App per dispositivi mobili supporta il controllo della concorrenza ottimistica monitorando le modifiche apportate a ogni elemento usando la colonna delle proprietà di sistema version
definita per ogni tabella nel back-end dell'app per dispositivi mobili. Ogni volta che un record viene aggiornato, App per dispositivi mobili imposta la proprietà version
per tale record su un nuovo valore. Durante ogni richiesta di aggiornamento, la proprietà version
del record incluso nella richiesta viene confrontata con la stessa proprietà per il record nel server. Se la versione passata con la richiesta non corrisponde al back-end, la libreria client genera un'eccezione MobileServicePreconditionFailedException<T>
. Il tipo incluso nell'eccezione è il record del back-end contenente la versione server del record. L'applicazione può quindi usare queste informazioni per decidere se eseguire nuovamente la richiesta di aggiornamento con il valore di version
corretto dal back-end per eseguire il commit delle modifiche.
Definire una colonna nella classe di tabella per la proprietà di sistema version
per abilitare la concorrenza ottimistica. Per esempio:
public class TodoItem
{
public string Id { get; set; }
[JsonProperty(PropertyName = "text")]
public string Text { get; set; }
[JsonProperty(PropertyName = "complete")]
public bool Complete { get; set; }
// *** Enable Optimistic Concurrency *** //
[JsonProperty(PropertyName = "version")]
public string Version { set; get; }
}
Le applicazioni che usano tabelle non tipate consentono la concorrenza ottimistica impostando il flag Version
sul SystemProperties
della tabella come indicato di seguito.
//Enable optimistic concurrency by retrieving version
todoTable.SystemProperties |= MobileServiceSystemProperties.Version;
Oltre ad abilitare la concorrenza ottimistica, è necessario rilevare anche l'eccezione MobileServicePreconditionFailedException<T>
nel codice quando si chiama UpdateAsync. Risolvere il conflitto applicando il version
corretto al record aggiornato e chiamare UpdateAsync con il record risolto. Il codice seguente illustra come risolvere un conflitto di scrittura una volta rilevato:
private async void UpdateToDoItem(TodoItem item)
{
MobileServicePreconditionFailedException<TodoItem> exception = null;
try
{
//update at the remote table
await todoTable.UpdateAsync(item);
}
catch (MobileServicePreconditionFailedException<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();
}
Per altre informazioni, vedere l'argomento Sincronizzazione dati offline in App per dispositivi mobili di Azure.
Associare dati a un'interfaccia utente di Windows
Questa sezione illustra come visualizzare gli oggetti dati restituiti usando elementi dell'interfaccia utente in un'app di Windows. Il codice di esempio seguente viene associato all'origine dell'elenco con una query per gli elementi incompleti. Il MobileServiceCollection crea una raccolta di binding compatibile con app per dispositivi mobili.
// This query filters out completed TodoItems.
MobileServiceCollection<TodoItem, TodoItem> items = await todoTable
.Where(todoItem => todoItem.Complete == false)
.ToCollectionAsync();
// itemsControl is an IEnumerable that could be bound to a UI list control
IEnumerable itemsControl = items;
// Bind this to a ListBox
ListBox lb = new ListBox();
lb.ItemsSource = items;
Alcuni controlli nel runtime gestito supportano un'interfaccia denominata ISupportIncrementalLoading. Questa interfaccia consente ai controlli di richiedere dati aggiuntivi quando l'utente scorre. È disponibile il supporto predefinito per questa interfaccia per le app di Windows universali tramite MobileServiceIncrementalLoadingCollection, che gestisce automaticamente le chiamate dai controlli. Usare MobileServiceIncrementalLoadingCollection
nelle app di Windows come indicato di seguito:
MobileServiceIncrementalLoadingCollection<TodoItem,TodoItem> items;
items = todoTable.Where(todoItem => todoItem.Complete == false).ToIncrementalLoadingCollection();
ListBox lb = new ListBox();
lb.ItemsSource = items;
Per usare la nuova raccolta nelle app di Windows Phone 8 e "Silverlight", usare i metodi di estensione ToCollection
su IMobileServiceTableQuery<T>
e IMobileServiceTable<T>
. Per caricare i dati, chiamare LoadMoreItemsAsync()
.
MobileServiceCollection<TodoItem, TodoItem> items = todoTable.Where(todoItem => todoItem.Complete==false).ToCollection();
await items.LoadMoreItemsAsync();
Quando si usa la raccolta creata chiamando ToCollectionAsync
o ToCollection
, si ottiene una raccolta che può essere associata ai controlli dell'interfaccia utente. Questa raccolta è compatibile con il paging. Poiché la raccolta carica i dati dalla rete, il caricamento a volte ha esito negativo. Per gestire tali errori, eseguire l'override del metodo OnException
su MobileServiceIncrementalLoadingCollection
per gestire le eccezioni risultanti dalle chiamate a LoadMoreItemsAsync
.
Si consideri se la tabella include molti campi, ma si desidera visualizzarne solo alcuni nel controllo. È possibile usare le indicazioni nella sezione precedente "Selezionare colonne specifiche" per selezionare colonne specifiche da visualizzare nell'interfaccia utente.
Modificare le dimensioni della pagina
App per dispositivi mobili di Azure restituisce un massimo di 50 elementi per ogni richiesta per impostazione predefinita. È possibile modificare le dimensioni del paging aumentando le dimensioni massime della pagina sia nel client che nel server. Per aumentare le dimensioni della pagina richiesta, specificare PullOptions
quando si usa PullAsync()
:
PullOptions pullOptions = new PullOptions
{
MaxPageSize = 100
};
Supponendo di aver effettuato il PageSize
uguale o maggiore di 100 all'interno del server, una richiesta restituisce fino a 100 elementi.
Usare le tabelle offline
Le tabelle offline usano un archivio SQLite locale per archiviare i dati da usare quando sono offline. Tutte le operazioni di tabella vengono eseguite sull'archivio SQLite locale anziché sull'archivio server remoto. Per creare una tabella offline, preparare prima di tutto il progetto.
- In Visual Studio fare clic con il pulsante destro del mouse sulla soluzione >Gestisci pacchetti NuGet per la soluzione, quindi cercare e installare il pacchetto NuGet Microsoft.Azure.Mobile.Client.SQLiteStore per tutti i progetti della soluzione.
- Per i dispositivi Windows, premere Riferimenti>Aggiungi riferimento..., espandere la cartella windows>Extensions, quindi abilitare la SQLite appropriata per Windows SDK insieme a Visual C++ 2013 Runtime per Windows SDK. I nomi di SQLite SDK variano leggermente con ogni piattaforma Windows.
Prima di poter creare un riferimento a una tabella, è necessario preparare l'archivio locale:
var store = new MobileServiceSQLiteStore(Constants.OfflineDbPath);
store.DefineTable<TodoItem>();
//Initializes the SyncContext using the default IMobileServiceSyncHandler.
await this.client.SyncContext.InitializeAsync(store);
L'inizializzazione dell'archivio viene in genere eseguita immediatamente dopo la creazione del client. Il OfflineDbPath deve essere un nome file adatto per l'uso in tutte le piattaforme supportate. Se il percorso è un percorso completo, ovvero inizia con una barra, viene usato tale percorso. Se il percorso non è completo, il file viene inserito in un percorso specifico della piattaforma.
- Per i dispositivi iOS e Android, il percorso predefinito è la cartella "File personali".
- Per i dispositivi Windows, il percorso predefinito è la cartella "AppData" specifica dell'applicazione.
È possibile ottenere un riferimento a una tabella usando il metodo GetSyncTable<>
:
var table = client.GetSyncTable<TodoItem>();
Non è necessario eseguire l'autenticazione per usare una tabella offline. È sufficiente eseguire l'autenticazione quando si comunica con il servizio back-end.
Sincronizzare una tabella offline
Le tabelle offline non vengono sincronizzate con il back-end per impostazione predefinita. La sincronizzazione viene suddivisa in due parti. È possibile eseguire il push delle modifiche separatamente dal download di nuovi elementi. Ecco un metodo di sincronizzazione tipico:
public async Task SyncAsync()
{
ReadOnlyCollection<MobileServiceTableOperationError> syncErrors = null;
try
{
await this.client.SyncContext.PushAsync();
await this.todoTable.PullAsync(
//The first parameter is a query name that is used internally by the client SDK to implement incremental sync.
//Use a different query name for each unique query in your program
"allTodoItems",
this.todoTable.CreateQuery());
}
catch (MobileServicePushFailedException exc)
{
if (exc.PushResult != null)
{
syncErrors = exc.PushResult.Errors;
}
}
// Simple error/conflict handling. A real application would handle the various errors like network conditions,
// server conflicts and others via the IMobileServiceSyncHandler.
if (syncErrors != null)
{
foreach (var error in syncErrors)
{
if (error.OperationKind == MobileServiceTableOperationKind.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"]);
}
}
}
Se il primo argomento da PullAsync
è Null, la sincronizzazione incrementale non viene usata. Ogni operazione di sincronizzazione recupera tutti i record.
L'SDK esegue un PushAsync()
implicito prima di eseguire il pull dei record.
La gestione dei conflitti avviene in un metodo di PullAsync()
. È possibile gestire i conflitti nello stesso modo delle tabelle online. Il conflitto viene generato quando viene chiamato PullAsync()
anziché durante l'inserimento, l'aggiornamento o l'eliminazione. Se si verificano più conflitti, vengono raggruppati in un'unica eccezione MobileServicePushFailedException. Gestire ogni errore separatamente.
Usare un'API personalizzata
Un'API personalizzata consente di definire endpoint personalizzati che espongono funzionalità del server che non eseguono il mapping a un'operazione di inserimento, aggiornamento, eliminazione o lettura. Usando un'API personalizzata, è possibile avere maggiore controllo sulla messaggistica, inclusa la lettura e l'impostazione di intestazioni di messaggio HTTP e la definizione di un formato del corpo del messaggio diverso da JSON.
Per chiamare un'API personalizzata, chiamare uno dei metodi InvokeApiAsync di
var result = await client.InvokeApiAsync<MarkAllResult>("completeAll", System.Net.Http.HttpMethod.Post, null);
Questo modulo è una chiamata al metodo tipizzata e richiede che sia definito il MarkAllResult tipo restituito. Sono supportati entrambi i metodi tipizzato e non tipizzato.
Il metodo InvokeApiAsync() antepone '/api/' all'API che si vuole chiamare, a meno che l'API non inizi con un '/'. Per esempio:
-
InvokeApiAsync("completeAll",...)
chiama /api/completeAll nel back-end -
InvokeApiAsync("/.auth/me",...)
chiama /.auth/me nel back-end
È possibile usare InvokeApiAsync per chiamare qualsiasi API Web, incluse quelle WebAPI non definite con App per dispositivi mobili di Azure. Quando si usa InvokeApiAsync(), le intestazioni appropriate, incluse le intestazioni di autenticazione, vengono inviate con la richiesta.
Autenticare gli utenti
App per dispositivi mobili supporta l'autenticazione e l'autorizzazione degli utenti di app usando vari provider di identità esterni: Facebook, Google, Account Microsoft, Twitter e Microsoft Entra ID. È possibile impostare le autorizzazioni per le tabelle per limitare l'accesso per operazioni specifiche solo agli utenti autenticati. È anche possibile usare l'identità degli utenti autenticati per implementare le regole di autorizzazione negli script del server.
Sono supportati due flussi di autenticazione: gestito dal client e flusso di gestito dal server. Il flusso gestito dal server offre l'esperienza di autenticazione più semplice, perché si basa sull'interfaccia di autenticazione Web del provider. Il flusso gestito dal client consente un'integrazione più approfondita con funzionalità specifiche del dispositivo perché si basa su SDK specifici del provider specifici del dispositivo.
Nota
È consigliabile usare un flusso gestito dal client nelle app di produzione.
Per configurare l'autenticazione, è necessario registrare l'app con uno o più provider di identità. Il provider di identità genera un ID client e un segreto client per l'app. Questi valori vengono quindi impostati nel back-end per abilitare l'autenticazione/autorizzazione del servizio app di Azure.
In questa sezione vengono trattati gli argomenti seguenti:
- di autenticazione gestita dal client
- 'autenticazione gestita dal server
- Memorizzazione nella cache del token di autenticazione
Autenticazione gestita dal client
L'app può contattare in modo indipendente il provider di identità e quindi fornire il token restituito durante l'accesso con il back-end. Questo flusso client consente di offrire un'esperienza Single Sign-On per gli utenti o recuperare dati utente aggiuntivi dal provider di identità. L'autenticazione del flusso client è preferibile all'uso di un flusso server, perché l'SDK del provider di identità offre un'esperienza utente più nativa e consente una maggiore personalizzazione.
Sono disponibili esempi per i modelli di autenticazione del flusso client seguenti:
- di Active Directory Authentication Library
- Facebook o Google
Autenticare gli utenti con Active Directory Authentication Library
È possibile usare Active Directory Authentication Library (ADAL) per avviare l'autenticazione utente dal client usando l'autenticazione di Microsoft Entra.
Avvertimento
Il supporto per Active Directory Authentication Library (ADAL) terminerà a dicembre 2022. Le app che usano ADAL nelle versioni del sistema operativo esistenti continueranno a funzionare, ma il supporto tecnico e gli aggiornamenti della sicurezza termineranno. Per altre informazioni, vedere Eseguire la migrazione di app a MSAL.
Configurare il back-end dell'app per dispositivi mobili per l'accesso a Microsoft Entra seguendo l'esercitazione Come configurare il servizio app per l'accesso di Active Directory. Assicurarsi di completare il passaggio facoltativo di registrazione di un'applicazione client nativa.
In Visual Studio aprire il progetto e aggiungere un riferimento al pacchetto NuGet
Microsoft.IdentityModel.Clients.ActiveDirectory
. Durante la ricerca, includere versioni non definitive.Aggiungere il codice seguente all'applicazione, in base alla piattaforma in uso. In ogni, effettuare le sostituzioni seguenti:
Sostituire INSERT-AUTHORITY-HERE con il nome del tenant in cui è stato effettuato il provisioning dell'applicazione. Il formato deve essere
https://login.microsoftonline.com/contoso.onmicrosoft.com
. Questo valore può essere copiato dalla scheda Dominio nell'ID Entra Microsoft nel [portale di Azure].Sostituire INSERT-RESOURCE-ID-HERE con l'ID client per il back-end dell'app per dispositivi mobili. È possibile ottenere l'ID client dalla scheda avanzate
in Impostazioni di Microsoft Entra nel portale. Sostituire INSERT-CLIENT-ID-HERE con l'ID client copiato dall'applicazione client nativa.
Sostituire INSERT-REDIRECT-URI-HERE con l'endpoint
/.auth/login/done
del sito usando lo schema HTTPS. Questo valore deve essere simile ahttps://contoso.azurewebsites.net/.auth/login/done
.Il codice necessario per ogni piattaforma è il seguente:
Windows:
private MobileServiceUser user; private async Task AuthenticateAsync() { string authority = "INSERT-AUTHORITY-HERE"; string resourceId = "INSERT-RESOURCE-ID-HERE"; string clientId = "INSERT-CLIENT-ID-HERE"; string redirectUri = "INSERT-REDIRECT-URI-HERE"; while (user == null) { string message; try { AuthenticationContext ac = new AuthenticationContext(authority); AuthenticationResult ar = await ac.AcquireTokenAsync(resourceId, clientId, new Uri(redirectUri), new PlatformParameters(PromptBehavior.Auto, false) ); JObject payload = new JObject(); payload["access_token"] = ar.AccessToken; user = await App.MobileService.LoginAsync( MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload); message = string.Format("You are now logged in - {0}", user.UserId); } catch (InvalidOperationException) { message = "You must log in. Login Required"; } var dialog = new MessageDialog(message); dialog.Commands.Add(new UICommand("OK")); await dialog.ShowAsync(); } }
Xamarin.iOS
private MobileServiceUser user; private async Task AuthenticateAsync(UIViewController view) { string authority = "INSERT-AUTHORITY-HERE"; string resourceId = "INSERT-RESOURCE-ID-HERE"; string clientId = "INSERT-CLIENT-ID-HERE"; string redirectUri = "INSERT-REDIRECT-URI-HERE"; try { AuthenticationContext ac = new AuthenticationContext(authority); AuthenticationResult ar = await ac.AcquireTokenAsync(resourceId, clientId, new Uri(redirectUri), new PlatformParameters(view)); JObject payload = new JObject(); payload["access_token"] = ar.AccessToken; user = await client.LoginAsync( MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload); } catch (Exception ex) { Console.Error.WriteLine(@"ERROR - AUTHENTICATION FAILED {0}", ex.Message); } }
Xamarin.Android
private MobileServiceUser user; private async Task AuthenticateAsync() { string authority = "INSERT-AUTHORITY-HERE"; string resourceId = "INSERT-RESOURCE-ID-HERE"; string clientId = "INSERT-CLIENT-ID-HERE"; string redirectUri = "INSERT-REDIRECT-URI-HERE"; try { AuthenticationContext ac = new AuthenticationContext(authority); AuthenticationResult ar = await ac.AcquireTokenAsync(resourceId, clientId, new Uri(redirectUri), new PlatformParameters(this)); JObject payload = new JObject(); payload["access_token"] = ar.AccessToken; user = await client.LoginAsync( MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload); } catch (Exception ex) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.SetMessage(ex.Message); builder.SetTitle("You must log in. Login Required"); builder.Create().Show(); } } protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) { base.OnActivityResult(requestCode, resultCode, data); AuthenticationAgentContinuationHelper.SetAuthenticationAgentContinuationEventArgs(requestCode, resultCode, data); }
Single Sign-On con un token di Facebook o Google
È possibile usare il flusso client come illustrato in questo frammento di codice per Facebook o Google.
var token = new JObject();
// Replace access_token_value with actual value of your access token obtained
// using the Facebook or Google SDK.
token.Add("access_token", "access_token_value");
private MobileServiceUser user;
private async Task AuthenticateAsync()
{
while (user == null)
{
string message;
try
{
// Change MobileServiceAuthenticationProvider.Facebook
// to MobileServiceAuthenticationProvider.Google if using Google auth.
user = await client.LoginAsync(MobileServiceAuthenticationProvider.Facebook, token);
message = string.Format("You are now logged in - {0}", user.UserId);
}
catch (InvalidOperationException)
{
message = "You must log in. Login Required";
}
var dialog = new MessageDialog(message);
dialog.Commands.Add(new UICommand("OK"));
await dialog.ShowAsync();
}
}
Autenticazione gestita dal server
Dopo aver registrato il provider di identità, chiamare il metodo LoginAsync
private MobileServiceUser user;
private async System.Threading.Tasks.Task Authenticate()
{
while (user == null)
{
string message;
try
{
user = await client
.LoginAsync(MobileServiceAuthenticationProvider.Facebook);
message =
string.Format("You are now logged in - {0}", user.UserId);
}
catch (InvalidOperationException)
{
message = "You must log in. Login Required";
}
var dialog = new MessageDialog(message);
dialog.Commands.Add(new UICommand("OK"));
await dialog.ShowAsync();
}
}
Se si usa un provider di identità diverso da Facebook, modificare il valore di MobileServiceAuthenticationProvider impostando il valore per il provider.
In un flusso del server il servizio app di Azure gestisce il flusso di autenticazione OAuth visualizzando la pagina di accesso del provider selezionato. Una volta restituito il provider di identità, servizio app di Azure genera un token di autenticazione del servizio app. Il metodo LoginAsync
Nota
Le app per dispositivi mobili di Azure usano un Xamarin.Essentials WebAuthenticator per eseguire il lavoro. È necessario gestire la risposta dal servizio chiamando di nuovo in Xamarin.Essentials. Per informazioni dettagliate, vedere WebAuthenticator.
Memorizzazione nella cache del token di autenticazione
In alcuni casi, la chiamata al metodo di accesso può essere evitata dopo la prima autenticazione riuscita archiviando il token di autenticazione dal provider. Le app di Microsoft Store e UWP possono usare PasswordVault per memorizzare nella cache il token di autenticazione corrente dopo un accesso corretto, come indicato di seguito:
await client.LoginAsync(MobileServiceAuthenticationProvider.Facebook);
PasswordVault vault = new PasswordVault();
vault.Add(new PasswordCredential("Facebook", client.currentUser.UserId,
client.currentUser.MobileServiceAuthenticationToken));
Il valore UserId viene archiviato come UserName della credenziale e il token viene archiviato come Password. Nelle start-up successive è possibile controllare la PasswordVault per le credenziali memorizzate nella cache. L'esempio seguente usa le credenziali memorizzate nella cache quando vengono trovate e in caso contrario tenta di eseguire nuovamente l'autenticazione con il back-end:
// Try to retrieve stored credentials.
var creds = vault.FindAllByResource("Facebook").FirstOrDefault();
if (creds != null)
{
// Create the current user from the stored credentials.
client.currentUser = new MobileServiceUser(creds.UserName);
client.currentUser.MobileServiceAuthenticationToken =
vault.Retrieve("Facebook", creds.UserName).Password;
}
else
{
// Regular login flow and cache the token as shown above.
}
Quando si disconnette un utente, è necessario rimuovere anche le credenziali archiviate, come indicato di seguito:
client.Logout();
vault.Remove(vault.Retrieve("Facebook", client.currentUser.UserId));
Quando si usa l'autenticazione gestita dal client, è anche possibile memorizzare nella cache il token di accesso ottenuto dal provider, ad esempio Facebook o Twitter. Questo token può essere fornito per richiedere un nuovo token di autenticazione dal back-end, come indicato di seguito:
var token = new JObject();
// Replace <your_access_token_value> with actual value of your access token
token.Add("access_token", "<your_access_token_value>");
// Authenticate using the access token.
await client.LoginAsync(MobileServiceAuthenticationProvider.Facebook, token);
Argomenti vari
Gestire gli errori
Quando si verifica un errore nel back-end, l'SDK client genera un MobileServiceInvalidOperationException
. L'esempio seguente illustra come gestire un'eccezione restituita dal back-end:
private async void InsertTodoItem(TodoItem todoItem)
{
// This code inserts a new TodoItem into the database. When the operation completes
// and App Service has assigned an ID, the item is added to the CollectionView
try
{
await todoTable.InsertAsync(todoItem);
items.Add(todoItem);
}
catch (MobileServiceInvalidOperationException e)
{
// Handle error
}
}
Personalizzare le intestazioni delle richieste
Per supportare lo scenario specifico dell'app, potrebbe essere necessario personalizzare la comunicazione con il back-end dell'app per dispositivi mobili. Ad esempio, è possibile aggiungere un'intestazione personalizzata a ogni richiesta in uscita o anche modificare i codici di stato delle risposte. È possibile usare un personalizzato DelegatingHandler, come nell'esempio seguente:
public async Task CallClientWithHandler()
{
MobileServiceClient client = new MobileServiceClient("AppUrl", new MyHandler());
IMobileServiceTable<TodoItem> todoTable = client.GetTable<TodoItem>();
var newItem = new TodoItem { Text = "Hello world", Complete = false };
await todoTable.InsertAsync(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;
}
}
Abilitare la registrazione delle richieste
È anche possibile usare un DelegatongHandler per aggiungere la registrazione delle richieste:
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;
}
}