Partager via


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

Note

Ce produit est mis hors service. Pour un remplacement des projets utilisant .NET 8 ou version ultérieure, consultez la bibliothèque Datasync Community Toolkit.

Ce guide vous montre comment effectuer des scénarios courants à l’aide de la bibliothèque cliente .NET pour Azure Mobile Apps. Utilisez la bibliothèque cliente .NET dans les applications Windows (WPF, UWP) ou Xamarin (Native ou Forms). Si vous débutez avec Azure Mobile Apps, commencez par suivre le didacticiel 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 relatif à 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 de l’API 30.
  • Xamarin.iOS version 8.0 à 14.3.
  • La plateforme Windows universelle génère 16299 et versions ultérieures.
  • Toute application .NET Standard 2.0.

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

Configuration et conditions préalables

Nous partons du principe que vous avez déjà créé et publié votre projet principal Azure Mobile Apps, qui inclut au moins une table. Dans le code utilisé dans cette rubrique, la table est nommée TodoItem et a une chaîne Id, et Text champs et une colonne de Complete booléenne. Cette table est la même table créée lorsque vous avez terminé le guide de démarrage rapide .

Le type côté client typé correspondant en C# est cette 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; }
}

Le JsonPropertyAttribute est utilisé pour définir le mappage PropertyName entre le champ client et le champ de table.

Pour savoir comment créer des tables dans votre back-end Mobile Apps, consultez la rubrique sdk .NET Server la rubrique Node.js Server SDK.

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

Cliquez avec le bouton droit sur votre projet, appuyez sur Gérer les packages NuGet, recherchez le package Microsoft.Azure.Mobile.Client, puis appuyez 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 crée l’objet MobileServiceClient utilisé pour accéder à votre back-end Mobile App.

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 des enregistrements et modifier les données dans la table. Les rubriques suivantes sont abordées :

Créer une référence de table

Tout le code qui accède ou modifie des données dans une table back-end 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 retourné utilise le modèle de sérialisation typé. Un modèle de sérialisation non typé est également pris en charge. L’exemple 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 vers le back-end Mobile App, qui inclut les fonctionnalités suivantes :

Note

Une taille de page pilotée par le serveur est appliquée pour empêcher tous les lignes d’être retournées. La pagination conserve les demandes par défaut pour les jeux de données volumineux d’impact négatif sur le service. Pour retourner plus de 50 lignes, utilisez la méthode Skip et Take, comme décrit dans Retourner des données dans les pages.

Filtrer les données retournées

Le code suivant montre comment filtrer les données en incluant une clause Where dans une requête. Elle retourne tous les éléments de todoTable dont la propriété Complete est égale à false. La fonction Where applique un prédicat de filtrage de lignes à la requête sur 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 serveur principal à l’aide de logiciels d’inspection des messages, tels que les outils de développement de navigateur ou Fiddler. Si vous examinez l’URI de la requête, notez que la chaîne de requête 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) Server :

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

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

// 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) Server :

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

Cette requête peut également être divisé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 qui doivent être traduites dans le sous-ensemble OData. Les opérations incluent :

  • Opérateurs relationnels (==, !=, <, <=, >, >=),
  • Opérateurs arithmétiques (+, -, /, *, %),
  • Précision numérique (Math.Floor, Math.Ceiling),
  • Fonctions de chaîne (Length, Substring, Replace, IndexOf, StartsWith, EndsWith),
  • Propriétés de date (Year, Month, Day, Hour, Minute, Second),
  • Accéder aux propriétés d’un objet et
  • Expressions combinant l’une de ces opérations.

Lorsque vous envisagez ce que prend en charge le Kit de développement logiciel (SDK) server, vous pouvez prendre en compte la documentation OData v3.

Trier les données retournées

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

Retourner des données dans des pages

Par défaut, le back-end retourne uniquement les 50 premières lignes. Vous pouvez augmenter le nombre de lignes retournées en appelant la méthode Take. Utilisez Take avec la méthode Skip pour demander une « page » spécifique du jeu de données total retourné par la requête. La requête suivante, lorsqu’elle est exécutée, retourne les trois premiers éléments du tableau.

// 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 suivante ignore les trois premiers résultats et retourne les trois résultats suivants. Cette requête produit la deuxième « page » de données, où la taille de la page 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 les enregistrements qui auraient été retournés, ignorant toute clause de pagination/limite spécifiée :

query = query.IncludeTotalCount();

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

Note

Pour remplacer la limite de 50 lignes dans un serveur principal d’application mobile, vous devez également appliquer la EnableQueryAttribute à la méthode GET publique et spécifier le comportement de pagination. En cas d’application à la méthode, les valeurs suivantes définissent la valeur maximale des lignes retournées sur 1 000 :

[EnableQuery(MaxTop=1000)]

Sélectionner des colonnes spécifiques

Vous pouvez spécifier l’ensemble de propriétés à inclure dans les résultats en ajoutant une clause Select à votre 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’à présent sont additifs. Nous pouvons donc continuer à les chaîner. Chaque appel chaîné affecte davantage la requête. Un autre exemple :

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 peut être utilisée pour rechercher des objets à partir de la base de données avec 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écuter des requêtes non typées

Lors de l’exécution d’une requête à l’aide d’un objet 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 récupérez des valeurs JSON que vous pouvez utiliser comme un conteneur de propriétés. Pour plus d’informations sur JToken et Newtonsoft Json, consultez le site Newtonsoft JSON.

Insérer des données

Tous les types de clients doivent contenir un membre nommé ID, qui est par défaut une chaîne. Cet id est nécessaire pour effectuer des opérations CRUD et pour la synchronisation hors connexion. Le code suivant montre comment utiliser la méthode InsertAsync pour insérer de nouvelles lignes dans une table. Le paramètre contient les données à insérer en tant qu’objet .NET.

await todoTable.InsertAsync(todoItem);

Si une valeur d’ID personnalisée unique n’est pas incluse dans le todoItem pendant une insertion, un GUID est généré par le serveur. Vous pouvez récupérer l’ID généré en inspectant 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 e-mail 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 des 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 des adresses e-mail ou des noms d’utilisateur pour l’ID. Les ID de chaîne vous offrent les avantages suivants :

  • Les ID sont générés sans aller-retour vers la base de données.
  • Les enregistrements sont plus faciles à fusionner à partir de différentes tables ou bases de données.
  • Les valeurs d’ID peuvent s’intégrer mieux à la logique d’une application.

Lorsqu’une valeur d’ID de chaîne n’est pas définie sur un enregistrement inséré, le back-end De l’application mobile 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, soit sur le client, soit dans le serveur principal.

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

Mettre à jour les données

Le code suivant montre comment utiliser la méthode UpdateAsync pour mettre à jour un enregistrement existant avec le même ID avec de nouvelles informations. Le paramètre contient les données à mettre à jour en tant qu’objet .NET.

await todoTable.UpdateAsync(todoItem);

Pour mettre à jour des données non typées, vous pouvez tirer parti 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);

Un champ id doit être spécifié lors de la création d’une mise à jour. Le serveur principal utilise le champ id pour identifier la ligne à mettre à jour. Le champ id peut être obtenu à partir du résultat de l’appel InsertAsync. Une ArgumentException est déclenchée si vous essayez de mettre à jour un élément sans fournir la valeur id.

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 sur le todoItem.

await todoTable.DeleteAsync(todoItem);

Pour supprimer des données non typées, vous pouvez tirer parti de Json.NET comme suit :

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

Lorsque vous effectuez une demande de suppression, un ID doit être spécifié. D’autres propriétés ne sont pas passées au service ou sont ignorées au niveau du service. Le résultat d’un appel DeleteAsync est généralement null. L’ID à transmettre peut être obtenu à partir du résultat de l’appel InsertAsync. Une MobileServiceInvalidOperationException est levée lorsque vous essayez de supprimer un élément sans spécifier le champ id.

Résolution des conflits et accès concurrentiel optimiste

Deux clients ou plus peuvent écrire des modifications dans le même élément en même temps. Sans détection de conflit, la dernière écriture remplacerait les mises à jour précédentes. contrôle d’accès concurrentiel optimiste suppose que chaque transaction peut valider et n’utilise donc aucun verrouillage de ressource. 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 restauré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 propriété système version définie pour chaque table de votre back-end Mobile App. Chaque fois qu’un enregistrement est mis à jour, Mobile Apps définit la propriété version pour cet enregistrement sur une nouvelle valeur. Pendant chaque demande de mise à jour, la propriété version de l’enregistrement inclus dans la requête est comparée à la même propriété pour l’enregistrement sur le serveur. Si la version passée avec la requête ne correspond pas au serveur principal, la bibliothèque cliente déclenche une exception MobileServicePreconditionFailedException<T>. Le type inclus à l’exception est l’enregistrement du back-end contenant la version des serveurs de l’enregistrement. L’application peut ensuite utiliser ces informations pour décider s’il faut réexécuter la demande de mise à jour avec la valeur de version correcte du serveur principal pour valider les modifications.

Définissez une colonne sur la classe de table pour la propriété système version pour activer l’accès concurrentiel optimiste. 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; }
}

Les applications utilisant des tables non typées permettent l’accès concurrentiel optimiste en définissant l’indicateur Version sur la SystemProperties de la table comme suit.

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

En plus d’activer l’accès concurrentiel optimiste, vous devez également intercepter l’exception MobileServicePreconditionFailedException<T> dans votre code lors de l’appel de UpdateAsync. Résolvez le conflit en appliquant la version correcte à 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 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 plus d’informations, consultez la rubrique Offline Data Sync dans Azure Mobile Apps rubrique.

Lier des données à une interface utilisateur Windows

Cette section montre comment afficher les objets de données retournés à l’aide d’éléments d’interface utilisateur dans une application Windows. L’exemple de code suivant lie à la source de la liste une requête pour les éléments incomplets. Le MobileServiceCollection crée 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 du runtime managé prennent en charge une interface appelée ISupportIncrementalLoading. Cette interface permet aux contrôles de demander des données supplémentaires lorsque l’utilisateur défile. Il existe une prise en charge intégrée de cette interface pour les applications Windows universelles via MobileServiceIncrementalLoadingCollection, qui gère automatiquement les appels à partir 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 sur IMobileServiceTableQuery<T> et IMobileServiceTable<T>. Pour charger des 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 en appelant 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. Étant donné que la collecte charge des données à partir du réseau, le chargement échoue parfois. Pour gérer ces échecs, remplacez la méthode OnException sur MobileServiceIncrementalLoadingCollection pour gérer les exceptions résultant d’appels à LoadMoreItemsAsync.

Déterminez si votre table comporte de nombreux champs, mais que vous souhaitez uniquement afficher certains d’entre eux dans votre contrôle. Vous pouvez utiliser les instructions de la section précédenteSélectionner des colonnes spécifiques» pour sélectionner des colonnes spécifiques à afficher dans l’interface utilisateur.

Modifier la taille de la page

Azure Mobile Apps retourne un maximum de 50 éléments par requête par défaut. Vous pouvez modifier la taille de pagination en augmentant la taille maximale de page sur le client et le serveur. Pour augmenter la taille de page demandée, spécifiez PullOptions lors de l’utilisation de PullAsync():

PullOptions pullOptions = new PullOptions
    {
        MaxPageSize = 100
    };

En supposant que vous avez effectué le PageSize égal ou supérieur à 100 au sein du serveur, une requête retourne jusqu’à 100 éléments.

Utiliser des tables hors connexion

Les tables hors connexion utilisent un magasin SQLite local pour stocker les données à utiliser en mode hors connexion. Toutes les opérations de table sont effectuées sur le magasin SQLite local au lieu du magasin de serveurs distants. 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 pour solution..., puis recherchez et installez le Microsoft.Azure.Mobile.Client.SQLiteStore package NuGet pour tous les projets de la solution.
  • Pour les appareils Windows, appuyez sur Références>Ajouter une référence..., développez le dossier Dossier Windows>Extensions, puis activez le kit de développement logiciel (SDK) SQLite pour Windows approprié, ainsi que le Kit de développement logiciel (SDK) Visual C++ 2013 Runtime pour Windows. Les noms du SDK SQLite varient légèrement avec chaque plateforme Windows.

Avant de créer une référence de table, le magasin local doit être préparé :

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

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

L’initialisation du magasin est normalement effectuée immédiatement après la création du client. Le OfflineDbPath doit être un nom de fichier adapté à une utilisation sur toutes les plateformes que vous prenez en charge. Si le chemin est un chemin complet (autrement dit, il commence par une barre oblique), ce chemin 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 « Fichiers personnels ».
  • Pour les appareils Windows, le chemin d’accès par défaut est le dossier « AppData » propre à l’application.

Vous pouvez obtenir une référence de table à l’aide de la méthode GetSyncTable<> :

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

Vous n’avez pas besoin de vous authentifier pour utiliser une table hors connexion. Vous devez uniquement vous authentifier lorsque vous communiquez avec le service principal.

Synchroniser une table hors connexion

Les tables hors connexion ne sont pas synchronisées avec le back-end par défaut. La synchronisation est divisée en deux parties. Vous pouvez envoyer des modifications séparément du téléchargement de nouveaux éléments. Voici une méthode de synchronisation classique :

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 à 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 d’enregistrements.

La gestion des conflits se produit sur une méthode PullAsync(). Vous pouvez gérer les conflits de la même façon que les tables en ligne. Le conflit est généré lorsque PullAsync() est appelé au lieu de l’insertion, de la mise à jour ou de la suppression. Si plusieurs conflits se produisent, ils sont regroupés dans un seul MobileServicePushFailedException. Gérez chaque défaillance séparément.

Utiliser une API personnalisée

Une API personnalisée vous permet de définir des points de terminaison personnalisés qui exposent les fonctionnalités de serveur qui ne correspondent pas à une opération d’insertion, de mise à jour, de suppression ou de lecture. En utilisant une API personnalisée, vous pouvez avoir plus de contrôle sur la messagerie, notamment la lecture et la définition des en-têtes de message HTTP et la définition d’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 au 'API completeAll sur le serveur principal :

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

Ce formulaire est un appel de méthode typé et nécessite que le type de retour MarkAllResult soit défini. Les méthodes typées et non typées sont prises en charge.

La méthode InvokeApiAsync() précède « /api / » à l’API que vous souhaitez appeler, sauf si l’API commence par un « / ». 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 n’importe quel WebAPI, y compris les WebAPIs qui ne sont pas définis 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 requête.

Authentifier les 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 des tables pour restreindre l’accès pour des opérations spécifiques aux utilisateurs authentifiés uniquement. Vous pouvez également utiliser l’identité des utilisateurs authentifiés pour implémenter des règles d’autorisation dans des scripts de serveur.

Deux flux d’authentification sont pris en charge : flux de géré par le client et de géré par le serveur . Le flux géré par le serveur offre l’expérience d’authentification la plus simple, car il s’appuie sur l’interface d’authentification web du fournisseur. Le flux géré par le client permet une intégration plus approfondie avec des fonctionnalités spécifiques à l’appareil, car il s’appuie sur des KITS SDK spécifiques à un fournisseur.

Note

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 auprès d’un ou de 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 back-end pour activer l’authentification/l’autorisation Azure App Service.

Les rubriques suivantes sont abordées dans cette section :

Authentification gérée par le client

Votre application peut contacter indépendamment le fournisseur d’identité, puis fournir le jeton retourné lors de la connexion avec votre back-end. Ce flux client vous permet de fournir une expérience d’authentification unique pour les utilisateurs ou de récupérer des données utilisateur supplémentaires à partir du fournisseur d’identité. L’authentification de flux client est préférable à l’utilisation d’un flux de serveur, car le SDK du fournisseur d’identité fournit une expérience utilisateur plus native et permet une personnalisation plus poussée.

Des exemples sont fournis pour les modèles d’authentification de flux client suivants :

Authentifier les utilisateurs avec la bibliothèque d’authentification Active Directory

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 utilisant ADAL sur les versions existantes du système d’exploitation continueront de fonctionner, mais le support technique et les mises à jour de sécurité se termineront. 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 Comment configurer App Service pour la connexion Active Directory tutoriel. Veillez à effectuer l’étape facultative d’inscription d’une application cliente native.

  2. Dans Visual Studio, ouvrez votre projet et ajoutez une référence au package NuGet Microsoft.IdentityModel.Clients.ActiveDirectory. Lors de la recherche, incluez des versions en préversion.

  3. Ajoutez le code suivant à votre application, en fonction de la plateforme que vous utilisez. Dans chacun d’eux, effectuez les remplacements suivants :

    • Remplacez INSERT-AUTHORITY-HERE par le nom du locataire dans lequel vous avez provisionné 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 de votre serveur principal d’application mobile. Vous pouvez obtenir l’ID client à partir de l’onglet Advanced sous Microsoft Entra Settings dans le portail.

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

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

      Le code nécessaire pour chaque plateforme suit :

      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 MobileServiceClient avec l'MobileServiceAuthenticationProvider valeur de votre fournisseur. Par exemple, le code suivant lance une connexion de flux de serveur à l’aide de 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 de serveur, Azure App Service gère le flux d’authentification OAuth en affichant la page de connexion du fournisseur sélectionné. Une fois le fournisseur d’identité retourné, Azure App Service génère un jeton d’authentification App Service. La méthode LoginAsync retourne une MobileServiceUser, qui fournit à la fois l’Id utilisateur authentifié de l’utilisateur authentifié et le MobileServiceAuthenticationToken, en tant que jeton web JSON (JWT). Ce jeton peut être mis en cache et réutilisé jusqu’à son expiration. Pour plus d’informations, consultez Mise en cache du jeton d’authentification.

Note

Sous les couvertures, Azure Mobile Apps utilise un Xamarin.Essentials WebAuthenticator pour effectuer le travail. 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, l’appel à la méthode de connexion peut être évité après la première authentification réussie en stockant le jeton d’authentification du fournisseur. Les applications Microsoft Store et UWP peuvent utiliser passwordVault pour mettre en cache le jeton d’authentification actuel 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 le mot de passe stocké. Lors des démarrages suivants, vous pouvez vérifier le de PasswordVault pour obtenir les informations d’identification mises en cache. L’exemple suivant utilise des informations d’identification mises en cache lorsqu’elles sont trouvées et tente sinon de s’authentifier à nouveau avec le 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.
}

Lorsque vous déconnectez un utilisateur, vous devez également supprimer les informations d’identification 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 auprès de votre fournisseur, tel que Facebook ou Twitter. Ce jeton peut être fourni pour demander un nouveau jeton d’authentification auprès du back-end, 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

Lorsqu’une erreur se produit dans le back-end, le SDK client déclenche une MobileServiceInvalidOperationException. L’exemple suivant montre comment gérer une exception retournée par le 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
    }
}

Personnaliser les en-têtes de requête

Pour prendre en charge votre scénario d’application spécifique, vous devrez peut-être personnaliser la communication avec le serveur principal de l’application mobile. Par exemple, vous pouvez ajouter un en-tête personnalisé à chaque demande sortante ou même modifier les codes d’état des réponses. Vous pouvez utiliser un DelegatingHandler personnalisé, 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 Gestionnaire de délégation 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;
    }
}