Partager via


Utilisation de la bibliothèque cliente Azure Mobile Apps v4.2.0 pour .NET

Ce guide vous montre comment traiter des scénarios courants mettant en jeu la bibliothèque cliente .NET pour Azure Mobile Apps. Utilisez la bibliothèque cliente .NET dans les applications Windows (WPF, UWP) ou Xamarin (natives ou forms). Si vous débutez avec Azure Mobile Apps, envisagez d’abord d’exécuter le tutoriel Démarrage rapide pour Xamarin.Forms.

Avertissement

Cet article décrit les informations relatives à la version de la bibliothèque v4.2.0, qui est superposée par la bibliothèque v5.0.0. Pour obtenir les informations les plus récentes, consultez l’article de la dernière version

Plateformes prises en charge

La bibliothèque cliente .NET prend en charge .NET Standard 2.0 et les plateformes suivantes :

  • Xamarin.Android du niveau d’API 19 jusqu’au niveau d’API 30.
  • Xamarin.iOS version 8.0 à 14.3.
  • Plateforme Windows universelle build 16299 ou ultérieure.
  • Toute application .NET Standard 2.0.

L’authentification « serveur-flow » utilise une vue web pour l’interface utilisateur présentée et peut ne pas être disponible sur chaque plateforme. Si elle n’est pas disponible, vous devez fournir une authentification « client-flow ». Cette bibliothèque cliente ne convient pas aux facteurs de forme watch ou IoT lors de l’utilisation de l’authentification.

Configuration et prérequis

Nous supposons que vous avez déjà créé et publié votre projet de back-end Azure Mobile Apps et qu’il comprend au moins une table. Dans le code utilisé dans cette rubrique, la table est nommée TodoItem et contient une chaîne Id, des champs Text et une colonne Complete booléenne. Il s’agit de la table créée à la fin du démarrage rapide.

Le type côté client typé correspondant en C# est la classe suivante :

public class TodoItem
{
    public string Id { get; set; }

    [JsonProperty(PropertyName = "text")]
    public string Text { get; set; }

    [JsonProperty(PropertyName = "complete")]
    public bool Complete { get; set; }
}

L’attribut JsonPropertyAttribute est utilisé pour définir le mappage PropertyName entre le champ client et le champ de la table.

Pour découvrir comment créer des tables dans votre back-end Mobile Apps, consultez la rubrique du kit SDK .NET Server et la rubrique du kit SDK Node.js Server.

Installer le package du Kit de développement logiciel (SDK) client géré

Cliquez avec le bouton droit sur votre projet, cliquez sur Gérer les packages NuGet, recherchez le package Microsoft.Azure.Mobile.Client, puis cliquez sur Installer. Pour les fonctionnalités hors connexion, installez également le package Microsoft.Azure.Mobile.Client.SQLiteStore.

Créer le client Azure Mobile Apps

Le code suivant permet de créer l’objet MobileServiceClient utilisé pour accéder à votre backend Mobile Apps.

var client = new MobileServiceClient("MOBILE_APP_URL");

Dans le code précédent, remplacez MOBILE_APP_URL par l’URL du back-end App Service. L’objet MobileServiceClient doit être un singleton.

Utiliser des tables

La section suivante explique comment rechercher et récupérer les enregistrements et modifier les données dans la table. Les rubriques suivantes sont traitées :

Créer une référence de table

L’ensemble du code permettant d’accéder aux données d’une table du backend ou de les modifier appelle des fonctions sur l’objet MobileServiceTable . Obtenez une référence à la table en appelant la méthode GetTable , comme suit :

IMobileServiceTable<TodoItem> todoTable = client.GetTable<TodoItem>();

L’objet renvoyé utilise le modèle de sérialisation typé. Les modèles de sérialisation non typés sont également pris en charge. L’exemple de code suivant crée une référence à une table non typée :

// Get an untyped table reference
IMobileServiceTable untypedTodoTable = client.GetTable("TodoItem");

Dans les requêtes non typées, vous devez spécifier la chaîne de requête OData sous-jacente.

Interroger des données à partir de votre application mobile

Cette section explique comment émettre des requêtes à destination du backend Mobile Apps, qui inclut les fonctionnalités suivantes :

Remarque

Une taille de page gérée par le serveur est imposée pour empêcher le renvoi de toutes les lignes. La pagination permet d'éviter que les requêtes par défaut associées à des jeux de données volumineux aient un impact négatif sur le service. Pour obtenir le renvoi de plus de 50 lignes, utilisez les méthodes Skip et Take, comme décrit dans la section Renvoyer les données dans les pages.

Filtrer les données renvoyées

Le code suivant montre comment filtrer des données en incluant une clause Where dans une requête. Il renvoie tous les éléments de todoTable dont la propriété Complete est égale à false. La fonction Where applique un prédicat de filtrage de ligne à la requête au niveau de la table.

// This query filters out completed TodoItems and items without a timestamp.
List<TodoItem> items = await todoTable
    .Where(todoItem => todoItem.Complete == false)
    .ToListAsync();

Vous pouvez afficher l’URI de la requête envoyée au backend en utilisant un logiciel d’inspection des messages, par exemple les outils destinés aux développeurs de navigateurs ou Fiddler. Si vous examinez l’URI de requête, vous remarquerez que la chaîne de requête elle-même est modifiée :

GET /tables/todoitem?$filter=(complete+eq+false) HTTP/1.1

Cette requête OData est traduite en requête SQL par le Kit de développement logiciel (SDK) de serveur :

SELECT *
    FROM TodoItem
    WHERE ISNULL(complete, 0) = 0

La fonction transmise à la méthode Where peut avoir un nombre de conditions arbitraire.

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

Cet exemple serait traduit en requête SQL par le Kit de développement logiciel (SDK) de serveur :

SELECT *
    FROM TodoItem
    WHERE ISNULL(complete, 0) = 0
          AND ISNULL(text, 0) = 0

Cette requête peut également être fractionnée en plusieurs clauses :

List<TodoItem> items = await todoTable
    .Where(todoItem => todoItem.Complete == false)
    .Where(todoItem => todoItem.Text != null)
    .ToListAsync();

Les deux méthodes sont équivalentes et peuvent être utilisées de manière interchangeable. L’ancienne option de concaténation de plusieurs prédicats dans une requête est plus compacte et recommandée.

La clause Where prend en charge les opérations traduites dans le sous-ensemble OData. Les opérations incluent :

  • des opérateurs relationnels (==, !=, <, <=, >, >=),
  • des opérateurs arithmétiques (+, -, /, *, %),
  • la précision des nombres (Math.Floor, Math.Ceiling),
  • des fonctions de chaîne (Length, Substring, Replace, IndexOf, StartsWith, EndsWith),
  • des propriétés de date (Year, Month, Day, Hour, Minute, Second),
  • les propriétés d’accès d’un objet, ainsi que
  • les expressions qui combinent toutes ces opérations.

Quand vous envisagez ce que le SDK Serveur prend en charge, vous pouvez consulter la documentation OData v3.

Trier les données renvoyées

Le code suivant montre comment trier les données en incluant une fonction OrderBy ou OrderByDescending dans la requête. Il renvoie des éléments de todoTable, triés par ordre croissant dans le champ 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();

Renvoyer les données de pages

Par défaut, le backend renvoie uniquement les 50 premières lignes. Vous pouvez augmenter le nombre de lignes renvoyées en appelant la méthode Take . Associez Take à la méthode Skip pour demander une « page » spécifique du jeu de données total renvoyé par la requête. Lorsqu'elle est exécutée, la requête suivante renvoie les trois premiers éléments de la table.

// Define a filtered query that returns the top 3 items.
MobileServiceTableQuery<TodoItem> query = todoTable.Take(3);
List<TodoItem> items = await query.ToListAsync();

La requête révisée ci-dessous ignore les trois premiers résultats et renvoie les trois résultats suivants. Cette requête produit la deuxième « page » de données, dont la taille est de trois éléments.

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

La méthode IncludeTotalCount demande le nombre total de tous les enregistrements qui auront été renvoyés, en ignorant toute clause de pagination/limite spécifiée :

query = query.IncludeTotalCount();

Dans une application réelle, vous pouvez utiliser des requêtes semblables à celles de l’exemple précédent avec un contrôle pager ou une interface utilisateur comparable pour permettre la navigation entre les pages.

Remarque

Pour remplacer la limite de 50 lignes dans un backend Mobile Apps, vous devez également appliquer l’attribut EnableQueryAttribute à la méthode GET publique et spécifier le comportement de pagination. Lorsqu'il est appliqué à la méthode, l'exemple suivant définit le nombre maximal de lignes renvoyées à 1 000 :

[EnableQuery(MaxTop=1000)]

Sélectionner des colonnes spécifiques

Vous pouvez indiquer le jeu de propriétés à inclure dans les résultats en ajoutant une clause Select à la requête. Par exemple, le code suivant montre comment sélectionner un seul champ et comment sélectionner et mettre en forme plusieurs champs :

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

Toutes les fonctions décrites jusqu’ici étant cumulatives, nous pouvons les concaténer. Chaque appel chaîné aura plus de répercussions sur la requête. Un exemple supplémentaire :

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

Rechercher des données par ID

La fonction LookupAsync permet de rechercher des objets dans la base de données à partir d'un ID particulier.

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

Exécution de requêtes non typées

Quand vous exécutez une requête avec un objet de table non typé, vous devez spécifier explicitement la chaîne de requête OData en appelant ReadAsync, comme dans l’exemple suivant :

// Lookup untyped data using OData
JToken untypedItems = await untypedTodoTable.ReadAsync("$filter=complete eq 0&$orderby=text");

Vous obtenez en retour des valeurs JSON que vous pouvez utiliser comme conteneur de propriétés. Pour plus d’informations sur JToken et Newtonsoft Json, consultez le site Newtonsoft JSON.

Insertion des données

Tous les types clients doivent contenir un membre nommé Id, par défaut une chaîne. Cet Id est requis pour effectuer des opérations CRUD et la synchronisation hors connexion. Le code suivant illustre l’utilisation de la méthode InsertAsync pour insérer de nouvelles lignes dans une table. Le paramètre contient les données à insérer sous forme d'objet .NET.

await todoTable.InsertAsync(todoItem);

Si aucune valeur ID personnalisée unique n’est incluse dans todoItem lors d’une insertion, le serveur génère un GUID. Vous pouvez récupérer l’ID généré en examinant l’objet après le retour de l’appel.

Pour insérer des données non typées, vous pouvez tirer parti de Json.NET :

JObject jo = new JObject();
jo.Add("Text", "Hello World");
jo.Add("Complete", false);
var inserted = await table.InsertAsync(jo);

Voici un exemple utilisant une adresse de messagerie comme ID de chaîne unique :

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

Utilisation de valeurs d'ID

Mobile Apps prend en charge des valeurs de chaîne personnalisées uniques pour la colonne id de la table. Une valeur de chaîne permet aux applications d’utiliser des valeurs personnalisées telles que les adresses de messagerie ou des noms d’utilisateur pour l’ID. Les ID de chaîne fournissent les avantages suivants :

  • Les ID sont générés sans effectuer d’aller-retour vers la base de données.
  • Il est plus facile de fusionner des enregistrements de plusieurs tables ou bases de données.
  • Les valeurs d’ID peuvent mieux s’intégrer à la logique d’une application.

Lorsque la valeur d’ID d’une chaîne n’est pas définie sur un enregistrement inséré, le backend Mobile Apps génère une valeur unique pour l’ID. Vous pouvez utiliser la méthode Guid.NewGuid pour générer vos propres valeurs d’ID, sur le client ou dans le backend.

JObject jo = new JObject();
jo.Add("id", Guid.NewGuid().ToString("N"));

Mettre à jour des données

Le code suivant montre comment utiliser la méthode UpdateAsync pour mettre à jour un enregistrement existant avec le même ID avec des informations nouvelles. Le paramètre contient les données à mettre à jour sous forme d'objet .NET.

await todoTable.UpdateAsync(todoItem);

Pour mettre à jour des données non typées, vous pouvez tirer profit de Newtonsoft JSON comme suit :

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

Vous devez spécifier un champ id si vous effectuez une mise à jour. Le backend utilise le champ id pour identifier la ligne à mettre à jour. Le champ id peut être obtenu à partir du résultat de l’appel de InsertAsync. Si vous essayez de mettre à jour un élément sans fournir de valeur ArgumentException, une id se déclenche.

Supprimer des données

Le code suivant montre comment utiliser la méthode DeleteAsync pour supprimer une instance existante. L’instance est identifiée par le champ id défini au niveau de todoItem.

await todoTable.DeleteAsync(todoItem);

Pour supprimer des données non typées, vous pouvez utiliser Json.NET ainsi :

JObject jo = new JObject();
jo.Add("id", "37BBF396-11F0-4B39-85C8-B319C729AF6D");
await table.DeleteAsync(jo);

Vous devez spécifier un ID lorsque vous effectuez une requête de suppression. Les autres propriétés ne sont pas transmises au service ou sont ignorées au niveau du service. Le résultat d’un appel DeleteAsync a généralement la valeur null. L'ID à transmettre peut être obtenu à partir du résultat de l'appel InsertAsync . Une MobileServiceInvalidOperationException est levée quand vous essayez de supprimer un élément sans spécifier le champ id.

Résolution des conflits et accès concurrentiel optimiste

Plusieurs clients peuvent écrire à un même moment des modifications dans un même élément. En l'absence de détection de conflits, la dernière écriture remplace les mises à jour précédentes. contrôle d'accès concurrentiel optimiste considère que chaque transaction peut être validée et, qu’à ce titre, elle ne fait appel à aucun verrouillage de ressources. Avant de valider une transaction, le contrôle d'accès concurrentiel optimiste vérifie qu'aucune autre transaction n'a modifié les données. Si les données ont été modifiées, la transaction de validation est annulée.

Mobile Apps prend en charge le contrôle d'accès concurrentiel optimiste en suivant les modifications apportées à chaque élément à l'aide de la colonne de la propriété système version définie pour chaque table de votre serveur principal Mobile Apps. Chaque fois qu’un enregistrement est mis à jour, Mobile Apps attribue une nouvelle valeur à la propriété version de cet enregistrement. À chaque demande de mise à jour, la propriété version de l'enregistrement inclus dans la demande est comparée à celle de l'enregistrement basé sur le serveur. Si la version transmise avec la demande ne correspond pas à celle du serveur principal, la bibliothèque cliente déclenche une exception MobileServicePreconditionFailedException<T> . Le type inclus avec l’exception est l’enregistrement du backend contenant la version serveur de l’enregistrement. À partir de cette information, l’application peut décider ou non d’exécuter à nouveau la requête de mise à jour avec la valeur version correcte du serveur principal pour valider les modifications.

Pour activer l’accès concurrentiel optimiste, l’application définit une colonne sur la classe table de la propriété système version . Par exemple :

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

Dans le cas des applications utilisant des tables non typées, l'accès concurrentiel optimiste est activé en définissant l'indicateur Version au niveau de la propriété SystemProperties de la table, comme suit.

//Enable optimistic concurrency by retrieving version
todoTable.SystemProperties |= MobileServiceSystemProperties.Version;

Outre l’activation de l’accès concurrentiel optimiste, vous devez également intercepter l’exception MobileServicePreconditionFailedException<T> dans votre code au moment de l’appel UpdateAsync. Résolvez le conflit en appliquant la version correcte pour l’enregistrement mis à jour et appelez UpdateAsync avec l’enregistrement résolu. Le code suivant montre comment résoudre un conflit d’écriture une fois qu’il est détecté :

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

Pour en savoir plus, consultez la rubrique Synchronisation des données hors connexion dans Azure Mobile Apps .

Lier des données à une interface utilisateur Windows

Cette section montre comment afficher des objets de données renvoyés à l'aide d'éléments d'interface utilisateur dans une application Windows. L’exemple de code suivant est lié à la source de la liste avec une requête pour les éléments incomplets. MobileServiceCollection permet de créer une collection de liaisons prenant en charge Mobile Apps.

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

Certains contrôles dans l’exécution gérée prennent en charge une interface appelée ISupportIncrementalLoading. Cette interface permet aux contrôles de demander des données supplémentaires lorsque l'utilisateur fait défiler l'écran. Une prise en charge de cette interface peut être intégrée aux applications Windows universelles par le biais de MobileServiceIncrementalLoadingCollection, qui traite automatiquement les appels en provenance des contrôles. Utilisez MobileServiceIncrementalLoadingCollection dans les applications Windows, comme suit :

MobileServiceIncrementalLoadingCollection<TodoItem,TodoItem> items;
items = todoTable.Where(todoItem => todoItem.Complete == false).ToIncrementalLoadingCollection();

ListBox lb = new ListBox();
lb.ItemsSource = items;

Pour utiliser la nouvelle collection sur les applications Windows Phone 8 et « Silverlight », utilisez les méthodes d’extension ToCollection au niveau de IMobileServiceTableQuery<T> et IMobileServiceTable<T>. Pour charger les données, appelez LoadMoreItemsAsync().

MobileServiceCollection<TodoItem, TodoItem> items = todoTable.Where(todoItem => todoItem.Complete==false).ToCollection();
await items.LoadMoreItemsAsync();

Lorsque vous utilisez la collection créée par l'appel de ToCollectionAsync ou ToCollection, vous obtenez une collection qui peut être liée aux contrôles d'interface utilisateur. Cette collection prend en charge la pagination. Comme la collection charge les données à partir du réseau, le chargement peut échouer. Pour gérer ces échecs, ignorez la méthode OnException au niveau de MobileServiceIncrementalLoadingCollection pour traiter les exceptions résultant des appels vers LoadMoreItemsAsync.

Imaginez que votre table contient de nombreux champs, mais que vous ne souhaitez en afficher qu'une partie dans votre contrôle. Vous pouvez suivre les instructions fournies dans la section précédente «Sélectionner des colonnes spécifiques» pour sélectionner les colonnes à afficher dans l’interface utilisateur.

Modifier la taille de la page

Par défaut, Azure Mobile Apps retourne au maximum 50 éléments par demande. Vous pouvez modifier la taille de pagination en augmentant la taille de page maximale à la fois sur le client et sur le serveur. Pour augmenter la taille de page demandée, spécifiez PullOptions lorsque vous utilisez PullAsync() :

PullOptions pullOptions = new PullOptions
    {
        MaxPageSize = 100
    };

Si vous définissez sur une valeur PageSize égale ou supérieure à 100 sur le serveur, une requête renvoie jusqu’à 100 éléments.

Utiliser des tables hors connexion

Les tables hors connexion utilisent une banque de données SQLite locale pour stocker les données pour une utilisation en mode hors connexion. Toutes les opérations de table sont effectuées sur la banque de données SQLite locale au lieu de la banque de données du serveur distant. Pour créer une table hors connexion, commencez par préparer votre projet.

  • Dans Visual Studio, cliquez avec le bouton droit sur la solution >Gérer les packages NuGet..., puis recherchez et installez le package NuGet Microsoft.Azure.Mobile.Client.SQLiteStore pour tous les projets de la solution.
  • Pour les appareils Windows, appuyez sur Références>Ajouter une référence..., développez les extensions de dossier >Windows, puis activez le KIT de développement logiciel (SDK) SQLite pour Windows approprié, ainsi que Visual C++ 2013 Runtime pour le Kit de développement logiciel (SDK) Windows. Les noms de SDK SQLite varient légèrement en fonction de la plateforme Windows utilisée.

Avant que vous ne puissiez créer une référence de table, la banque de données locale doit être préparée :

var store = new MobileServiceSQLiteStore(Constants.OfflineDbPath);
store.DefineTable<TodoItem>();

//Initializes the SyncContext using the default IMobileServiceSyncHandler.
await this.client.SyncContext.InitializeAsync(store);

L’initialisation de la banque de données est normalement effectuée immédiatement après la création du client. L’OfflineDbPath doit être un nom de fichier approprié pour une utilisation sur toutes les plates-formes prises en charge. Si le chemin d’accès est un chemin d’accès qualifié complet (autrement dit, s’il commence par une barre oblique), ce chemin d’accès est utilisé. Si le chemin d’accès n’est pas complet, le fichier est placé dans un emplacement spécifique à la plateforme.

  • Pour les appareils iOS et Android, le chemin d’accès par défaut est le dossier des fichiers personnels.
  • Pour les appareils Windows, le chemin d’accès par défaut est le dossier « AppData » spécifiques à l’application.

Une référence de table peut être obtenue à l’aide de la GetSyncTable<> méthode :

var table = client.GetSyncTable<TodoItem>();

Il est inutile de s’authentifier pour utiliser une table hors connexion. Il vous suffit de vous authentifier lorsque vous communiquez avec le service backend.

Synchroniser une table hors connexion

Les tables hors connexion ne sont pas synchronisées avec le backend par défaut. La synchronisation est divisée en deux parties. Vous pouvez transmettre des modifications en dehors du processus de téléchargement de nouveaux éléments. Voici une méthode de synchronisation typique :

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

Si le premier argument de PullAsync est null, la synchronisation incrémentielle n’est pas utilisée. Chaque opération de synchronisation récupère tous les enregistrements.

Le Kit de développement logiciel (SDK) effectue une PushAsync() implicite avant l’extraction des enregistrements.

La gestion des conflits s’effectue par le biais d’une méthode PullAsync(). Vous pouvez traiter les conflits de la même manière que les tables en ligne. Le conflit est généré lorsque PullAsync() est appelée à la place de ou pendant l’insertion, la mise à jour ou la suppression. Si plusieurs conflits se produisent, les opérations sont regroupées dans une seule MobileServicePushFailedException. Gérez chaque défaillance séparément.

Utilisation d’une API personnalisée

Une API personnalisée vous permet de définir des points de terminaison exposant une fonctionnalité de serveur qui ne mappe pas vers une opération d'insertion, de mise à jour, de suppression ou de lecture. En utilisant une API personnalisée, vous pouvez exercer davantage de contrôle sur la messagerie, notamment lire et définir des en-têtes de message HTTP et définir un format de corps de message autre que JSON.

Vous appelez une API personnalisée en appelant l'une des méthodes InvokeApiAsync sur le client. Par exemple, la ligne de code suivante envoie une requête POST à l’API completeAll sur le backend :

var result = await client.InvokeApiAsync<MarkAllResult>("completeAll", System.Net.Http.HttpMethod.Post, null);

Il s’agit d’un appel de méthode typé pour lequel le type de renvoi MarkAllResult doit être défini. Les méthodes typées et non typées sont toutes deux prises en charge.

La méthode InvokeApiAsync() ajoute « /api/ » à l’API que vous souhaitez appeler, sauf si l’API commence par « / ». Par exemple :

  • InvokeApiAsync("completeAll",...) appelle /api/completeAll sur le serveur principal
  • InvokeApiAsync("/.auth/me",...) appelle /.auth/me sur le serveur principal

Vous pouvez utiliser InvokeApiAsync pour appeler des API web, y compris si celles-ci ne sont pas définies avec Azure Mobile Apps. Lorsque vous utilisez InvokeApiAsync(), les en-têtes appropriés, y compris les en-têtes d’authentification, sont envoyés avec la demande.

Authentifier des utilisateurs

Mobile Apps prend en charge l’authentification et l’autorisation des utilisateurs d’applications à l’aide de différents fournisseurs d’identité externes : Facebook, Google, Compte Microsoft, Twitter et Microsoft Entra ID. Vous pouvez définir des autorisations sur les tables pour limiter l'accès à certaines opérations aux seuls utilisateurs authentifiés. Vous pouvez également utiliser l’identité des utilisateurs authentifiés pour implémenter des règles d’autorisation dans les scripts serveur.

Deux flux d’authentification sont pris en charge : le flux géré par le client et le flux géré par le serveur. Le flux géré par le serveur fournit l'authentification la plus simple, car il repose sur l'interface d'authentification Web du fournisseur. En revanche, le flux géré par le client est celui qui s'intègre le plus profondément aux fonctionnalités propres à l'appareil, car il s'appuie sur les Kits de développement logiciel (SDK) propres au fournisseur et à l'appareil.

Remarque

Nous vous recommandons d’utiliser un flux géré par le client dans vos applications de production.

Pour configurer l’authentification, vous devez inscrire votre application avec un ou plusieurs fournisseurs d’identité. Le fournisseur d’identité génère un ID client et une clé secrète client pour votre application. Ces valeurs sont ensuite définies dans votre backend pour activer l’authentification/autorisation d’Azure App Service.

Les rubriques suivantes sont traitées dans cette section :

Authentification gérée par le client

Votre application peut contacter le fournisseur d’identité de manière indépendante, puis fournir le jeton renvoyé pendant la connexion à votre back-end. Ce flux client permet de proposer l’authentification unique aux utilisateurs ou de récupérer des données utilisateur supplémentaires auprès du fournisseur d’identité. L’authentification par flux client est préférable à l’utilisation d’un flux serveur, car le kit SDK du fournisseur d’identité offre une interface UX plus native et permet une meilleure personnalisation.

Des exemples sont fournis pour les modèles suivants d’authentification gérée par le client :

Authentifier des utilisateurs avec la bibliothèque ADAL (Active Directory Authentication Library)

Vous pouvez utiliser la bibliothèque d’authentification Active Directory (ADAL) pour lancer l’authentification utilisateur à partir du client à l’aide de l’authentification Microsoft Entra.

Avertissement

La prise en charge de la Bibliothèque d’authentification Active Directory (ADAL) prendra fin en décembre 2022. Les applications qui utilisent ADAL sur des versions de système d’exploitation existantes continueront de fonctionner après cette date, mais elles ne bénéficieront plus du support technique ni des mises à jour de sécurité. Pour plus d’informations, consultez Migrer des applications vers MSAL.

  1. Configurez votre back-end d’application mobile pour l’authentification Microsoft Entra en suivant le didacticiel de configuration d’App Service pour Active Directory Login. Bien que cette étape soit facultative, veillez à inscrire une application cliente native.

  2. Dans Visual Studio, ouvrez votre projet et ajoutez une référence au package NuGet Microsoft.IdentityModel.Clients.ActiveDirectory. Au cours de la recherche, incluez les versions préliminaires.

  3. Ajoutez le code suivant à votre application, en fonction de la plateforme utilisée. Dans chaque cas, effectuez les remplacements suivants :

    • Remplacez INSERT-AUTHORITY-HERE par le nom du client dans lequel vous avez approvisionné votre application. Le format doit être https://login.microsoftonline.com/contoso.onmicrosoft.com. Cette valeur peut être copiée à partir de l’onglet Domaine de votre ID Microsoft Entra dans le [Portail Azure].

    • Remplacez INSERT-RESOURCE-ID-HERE par l’ID client du serveur principal de votre application mobile. Vous pouvez obtenir l’ID client à partir de l’onglet Avancé sous Microsoft Entra Paramètres dans le portail.

    • Remplacez INSERT-CLIENT-ID-HERE par l’ID client que vous avez copié depuis l’application cliente native.

    • Remplacez INSERT-REDIRECT-URI-HERE par le point de terminaison /.auth/login/done de votre site, en utilisant le schéma HTTPS. Cette valeur doit être similaire à https://contoso.azurewebsites.net/.auth/login/done.

      Voici le code nécessaire pour chaque plateforme :

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

Authentification unique à l’aide d’un jeton de Facebook ou Google

Vous pouvez utiliser le flux client comme indiqué dans cet extrait de code pour Facebook ou 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();
    }
}

Authentification gérée par le serveur

Une fois que vous avez inscrit votre fournisseur d’identité, appelez la méthode LoginAsync sur le MobileServiceClient avec la valeur MobileServiceAuthenticationProvider de votre fournisseur. Par exemple, le code suivant initie une connexion de flux serveur via Facebook.

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

Si vous utilisez un fournisseur d'identité autre que Facebook, remplacez la valeur de MobileServiceAuthenticationProvider par la valeur de votre fournisseur.

Dans un flux serveur, Azure App Service gère le flux d’authentification OAuth en affichant la page de connexion du fournisseur sélectionné. Après le retour du fournisseur identité, Azure App Service génère un jeton d’authentification App Service. La méthode LoginAsync renvoie un MobileServiceUser, qui fournit à la fois l’ID utilisateur de l’utilisateur authentifié et le jeton MobileServiceAuthenticationToken, sous la forme d’un jeton web JSON (JWT). Ce jeton peut être mis en cache et réutilisé jusqu'à ce qu'il arrive à expiration. Pour plus d'informations, consultez la section Mise en cache du jeton d'authentification.

Remarque

En coulisses, Azure Mobile Apps utilise un WebAuthenticator Xamarin.Essentials pour effectuer la tâche. Vous devez gérer la réponse du service en appelant Xamarin.Essentials. Pour plus d’informations, consultez WebAuthenticator.

Mise en cache du jeton d’authentification

Dans certains cas, il est possible d’éviter l’appel à la méthode de connexion après la première authentification réussie en stockant le jeton d’authentification à partir du fournisseur. Les applications Microsoft Store et UWP peuvent utiliser PasswordVault pour mettre en cache le jeton d’authentification en cours après une connexion réussie, comme suit :

await client.LoginAsync(MobileServiceAuthenticationProvider.Facebook);

PasswordVault vault = new PasswordVault();
vault.Add(new PasswordCredential("Facebook", client.currentUser.UserId,
    client.currentUser.MobileServiceAuthenticationToken));

La valeur UserId est stockée en tant que nom d’utilisateur des informations d’identification et le jeton est stocké en tant que mot de passe. Lors des démarrages suivants, vous pouvez vérifier les informations d’identification mises en cache dans PasswordVault. L’exemple suivant utilise les informations d’identification mises en cache si elles sont trouvées. Sinon, il tente à nouveau l’authentification avec le backend :

// 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.
}

Lorsque vous déconnectez un utilisateur, vous devez également supprimer les informations d’identifications stockées, comme suit :

client.Logout();
vault.Remove(vault.Retrieve("Facebook", client.currentUser.UserId));

Lorsque vous utilisez l’authentification gérée par le client, vous pouvez également mettre en cache le jeton d’accès obtenu à partir du fournisseur (par exemple, Facebook ou Twitter). Ce jeton peut être fourni pour demander un nouveau jeton d’authentification à partir du backend, comme suit :

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

Rubriques diverses

Gérer les erreurs

Quand une erreur se produit sur le backend, le Kit de développement logiciel (SDK) client déclenche une MobileServiceInvalidOperationException. L’exemple suivant montre comment gérer une exception renvoyée par le backend :

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

Personnaliser des en-têtes de demande

Pour prendre en charge votre scénario d’application en particulier, vous devrez peut-être personnaliser la communication avec le backend Mobile Apps. Par exemple, il est possible que vous vouliez ajouter un en-tête personnalisé à chaque demande sortante ou même modifier le code d'état des réponses. Pour cela, utilisez un DelegatingHandlerpersonnalisé, comme dans l'exemple suivant :

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

Activer la journalisation des demandes

Vous pouvez également utiliser un DelegatingHandler pour ajouter la journalisation des demandes :

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