Créer des services backend pour les applications mobiles natives avec ASP.NET Core

Par James Montemagno

Les applications mobiles peuvent communiquer avec les services back-end ASP.NET Core. Pour obtenir des instructions sur la connexion de services web locaux à partir de simulateurs iOS et d’émulateurs Android, consultez Se connecter à des services web locaux à partir de simulateurs iOS et d’émulateurs Android.

Afficher ou télécharger l’exemple de code de services backend

Exemple d’application mobile native

Ce didacticiel présente comment créer des services back-end en utilisant ASP.NET Core pour prendre en charge des applications mobiles natives. Il utilise l’application Xamarin.Forms TodoREST comme client natif, qui inclut des clients natifs distincts pour des appareils Android, iOS et Windows. Vous pouvez suivre le tutoriel lié pour créer l’application native (et installer les outils Xamarin gratuits nécessaires) et télécharger l’exemple de solution Xamarin. L’exemple Xamarin inclut un projet de services API web ASP.NET Core que l’application ASP.NET Core de cet article remplace (sans que des modifications soient requises par le client).

Application TodoREST s’exécutant sur un smartphone Android

Fonctionnalités

L’application TodoREST prend en charge l’énumération, l’ajout, la suppression et la mise à jour d’éléments To-Do. Chaque élément a un ID, un nom, des remarques et une propriété qui indique si la tâche a été effectuée.

Dans l’exemple précédent, la vue principale des éléments répertorie le nom de chaque élément et indique si la tâche est effectuée avec une marque.

Le fait d’appuyer sur l'icône+ ouvre une boîte de dialogue permettant l’ajout d’un élément :

Boîte de dialogue pour ajouter un élément

Le fait de cliquer sur un élément de l’écran de la liste principale ouvre une boîte de dialogue où les valeurs pour Name, Notes et Done peuvent être modifiées, et où vous pouvez supprimer l’élément :

Boîte de dialogue pour modifier un élément

Pour le tester vous-même par rapport à l’application ASP.NET Core créée dans la section suivante et exécutée sur votre ordinateur, mettez à jour la constante RestUrl de l’application.

Les émulateurs Android ne s’exécutent pas sur l’ordinateur local et utilisent une adresse IP de bouclage (10.0.2.2) pour communiquer avec l’ordinateur local. Tirez parti de Xamarin.Essentials DeviceInfo pour détecter le système d’exploitation qui s’exécute afin d’utiliser l’URL correcte.

Accédez au projet TodoREST et ouvrez le fichier Constants.cs. Le fichier Constants.cs contient la configuration qui suit.

using Xamarin.Essentials;
using Xamarin.Forms;

namespace TodoREST
{
    public static class Constants
    {
        // URL of REST service
        //public static string RestUrl = "https://YOURPROJECT.azurewebsites.net:8081/api/todoitems/{0}";

        // URL of REST service (Android does not use localhost)
        // Use http cleartext for local deployment. Change to https for production
        public static string RestUrl = DeviceInfo.Platform == DevicePlatform.Android ? "http://10.0.2.2:5000/api/todoitems/{0}" : "http://localhost:5000/api/todoitems/{0}";
    }
}

Vous pouvez éventuellement déployer le service web sur un service cloud tel qu’Azure et mettre à jour le RestUrl.

Création du projet ASP.NET Core

réez une application web ASP.NET Core dans Visual Studio. Choisissez le modèle d’API web. Nommez le projet TodoAPI.

Boîte de dialogue Nouvelle application web ASP.NET avec le modèle de projet API web sélectionné

L’application doit répondre à toutes les demandes effectuées sur le port 5000, y compris le trafic HTTP en texte clair pour notre client mobile. Mettez à jour Startup.cs pour que UseHttpsRedirection ne s’exécute donc pas dans le développement :

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        // For mobile apps, allow http traffic.
        app.UseHttpsRedirection();
    }

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

Remarque

Exécutez l’application directement, plutôt que derrière IIS Express. IIS Express ignore les requêtes non locales par défaut. Exécutez dotnet run à partir d’une invite de commandes ou choisissez le profil du nom d’application dans la liste déroulante Cible de débogage de la barre d’outils Visual Studio.

Ajoutez une classe de modèle pour représenter des éléments de tâche à effectuer. Marquez les champs obligatoires avec l’attribut [Required] :

using System.ComponentModel.DataAnnotations;

namespace TodoAPI.Models
{
    public class TodoItem
    {
        [Required]
        public string ID { get; set; }

        [Required]
        public string Name { get; set; }

        [Required]
        public string Notes { get; set; }

        public bool Done { get; set; }
    }
}

Les méthodes d’API requièrent un moyen d’utiliser des données. Utilisez la même interface ITodoRepository que celle utilisée par l’exemple Xamarin d’origine :

using System.Collections.Generic;
using TodoAPI.Models;

namespace TodoAPI.Interfaces
{
    public interface ITodoRepository
    {
        bool DoesItemExist(string id);
        IEnumerable<TodoItem> All { get; }
        TodoItem Find(string id);
        void Insert(TodoItem item);
        void Update(TodoItem item);
        void Delete(string id);
    }
}

Pour cet exemple, l’implémentation utilise simplement une collection privée d’éléments :

using System.Collections.Generic;
using System.Linq;
using TodoAPI.Interfaces;
using TodoAPI.Models;

namespace TodoAPI.Services
{
    public class TodoRepository : ITodoRepository
    {
        private List<TodoItem> _todoList;

        public TodoRepository()
        {
            InitializeData();
        }

        public IEnumerable<TodoItem> All
        {
            get { return _todoList; }
        }

        public bool DoesItemExist(string id)
        {
            return _todoList.Any(item => item.ID == id);
        }

        public TodoItem Find(string id)
        {
            return _todoList.FirstOrDefault(item => item.ID == id);
        }

        public void Insert(TodoItem item)
        {
            _todoList.Add(item);
        }

        public void Update(TodoItem item)
        {
            var todoItem = this.Find(item.ID);
            var index = _todoList.IndexOf(todoItem);
            _todoList.RemoveAt(index);
            _todoList.Insert(index, item);
        }

        public void Delete(string id)
        {
            _todoList.Remove(this.Find(id));
        }

        private void InitializeData()
        {
            _todoList = new List<TodoItem>();

            var todoItem1 = new TodoItem
            {
                ID = "6bb8a868-dba1-4f1a-93b7-24ebce87e243",
                Name = "Learn app development",
                Notes = "Take Microsoft Learn Courses",
                Done = true
            };

            var todoItem2 = new TodoItem
            {
                ID = "b94afb54-a1cb-4313-8af3-b7511551b33b",
                Name = "Develop apps",
                Notes = "Use Visual Studio and Visual Studio for Mac",
                Done = false
            };

            var todoItem3 = new TodoItem
            {
                ID = "ecfa6f80-3671-4911-aabe-63cc442c1ecf",
                Name = "Publish apps",
                Notes = "All app stores",
                Done = false,
            };

            _todoList.Add(todoItem1);
            _todoList.Add(todoItem2);
            _todoList.Add(todoItem3);
        }
    }
}

Configurez l’implémentation dans Startup.cs :

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<ITodoRepository, TodoRepository>();
    services.AddControllers();
}

Création du contrôleur

Ajoutez un nouveau contrôleur au projet, TodoItemsController. Il doit hériter de ControllerBase. Ajoutez un attribut Route pour indiquer que le contrôleur gère les demandes effectuées via des chemins commençant par api/todoitems. Le jeton [controller] de la route est remplacé par le nom du contrôleur (en omettant le suffixe Controller) et est particulièrement pratique pour les routes globales. Découvrez plus d’informations sur le routage.

Le contrôleur nécessite un ITodoRepository pour fonctionner ; demandez une instance de ce type via le constructeur du contrôleur. À l’exécution, cette instance est fournie via la prise en charge par l’infrastructure de l’injection de dépendances.

[ApiController]
[Route("api/[controller]")]
public class TodoItemsController : ControllerBase
{
    private readonly ITodoRepository _todoRepository;

    public TodoItemsController(ITodoRepository todoRepository)
    {
        _todoRepository = todoRepository;
    }

Cette API prend en charge quatre verbes HTTP différents pour effectuer des opérations CRUD (création, lecture, mise à jour, suppression) sur la source de données. La plus simple d’entre elles est l’opération de lecture, qui correspond à une requête HTTP GET.

Tester l’API à l’aide de curl

Vous pouvez tester la méthode d’API à l’aide d’un large éventail d’outils. Pour ce tutoriel, les outils en ligne de commande open source suivants sont utilisés :

  • curl : transfère des données à l’aide de différents protocoles, notamment HTTP et HTTPS. curl est utilisé dans ce tutoriel pour appeler l’API à l’aide de méthodes HTTP GET, POST, PUT et DELETE.
  • jq : processeur JSON utilisé dans ce tutoriel pour mettre en forme des données JSON afin qu’elles soient faciles à lire à partir de la réponse de l’API.

Installer curl et jq

curl est préinstallé sur macOS et est utilisé directement dans l’application macOS Terminal. Pour plus d’informations sur l’installation de curl, consultez le site web officiel de curl.

jq peut être installé à partir de Homebrew dans le terminal :

Installez Homebrew, s’il n’est pas déjà installé, avec la commande suivante :

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Suivez les instructions du programme d’installation.

Installez jq à l’aide de Homebrew avec la commande suivante :

brew install jq

Pour plus d’informations sur l’installation de Homebrew et jq, consultez Homebrew et jq.

Lecture d’éléments

Demander une liste d’éléments se fait via une requête GET à la méthode List. L’attribut [HttpGet] sur la méthode List indique que cette action doit gérer seulement les requêtes GET. La route pour cette action est la route spécifiée sur le contrôleur. Le nom de l’action ne doit pas nécessairement constituer une partie de la route. Il vous suffit de faire en sorte que chaque action ait une route unique et non ambiguë. Des attributs de routage peuvent être appliqués aux niveaux du contrôleur et des méthodes pour créer des routes spécifiques.

[HttpGet]
public IActionResult List()
{
    return Ok(_todoRepository.All);
}

Dans le terminal, entrez la commande curl suivante :

curl -v -X GET 'http://localhost:5000/api/todoitems/' | jq

La commande curl précédente inclut les composants suivants :

  • -v : active le mode verbose, fournissant des informations détaillées sur la réponse HTTP et est utile pour le test et la résolution des problèmes d’API.
  • -X GET : spécifie l’utilisation de la méthode HTTP GET pour la requête. Bien que curl puisse souvent déduire la méthode HTTP prévue, cette option la rend explicite.
  • 'http://localhost:5000/api/todoitems/' : il s’agit de l’URL cible de la requête. Dans cette instance, il s’agit d’un point de terminaison d’API REST.
  • | jq : ce segment n’est pas lié directement à curl. Le canal | est un opérateur d’interpréteur de commandes qui prend la sortie de la commande à gauche et l’envoie vers la commande à droite. jq est un processeur JSON de ligne de commande. Bien qu’il ne soit pas nécessaire, jq facilite la lecture des données JSON retournées.

La méthode List retourne un code de réponse 200 OK et tous les éléments Todo, sérialisés au format JSON :

[
  {
    "id": "6bb8a868-dba1-4f1a-93b7-24ebce87e243",
    "name": "Learn app development",
    "notes": "Take Microsoft Learn Courses",
    "done": true
  },
  {
    "id": "b94afb54-a1cb-4313-8af3-b7511551b33b",
    "name": "Develop apps",
    "notes": "Use Visual Studio and Visual Studio for Mac",
    "done": false
  },
  {
    "id": "ecfa6f80-3671-4911-aabe-63cc442c1ecf",
    "name": "Publish apps",
    "notes": "All app stores",
    "done": false
  }
]

Création d’éléments

Par convention, la création d’éléments de données est mappée au verbe HTTP POST. Un attribut [HttpPost] est appliqué à la méthode Create, laquelle accepte une instance TodoItem. Comme l’argument item est passé dans le corps de la requête POST, ce paramètre spécifie l’attribut [FromBody].

À l’intérieur de la méthode, la validité et l’existence préalable de l’élément dans le magasin de données sont vérifiées et, si aucun problème ne se produit, il est ajouté via le référentiel. La vérification ModelState.IsValid effectue la validation du modèle et doit être effectuée dans chaque méthode d’API qui accepte une entrée utilisateur.

[HttpPost]
public IActionResult Create([FromBody]TodoItem item)
{
    try
    {
        if (item == null || !ModelState.IsValid)
        {
            return BadRequest(ErrorCode.TodoItemNameAndNotesRequired.ToString());
        }
        bool itemExists = _todoRepository.DoesItemExist(item.ID);
        if (itemExists)
        {
            return StatusCode(StatusCodes.Status409Conflict, ErrorCode.TodoItemIDInUse.ToString());
        }
        _todoRepository.Insert(item);
    }
    catch (Exception)
    {
        return BadRequest(ErrorCode.CouldNotCreateItem.ToString());
    }
    return Ok(item);
}

L’exemple utilise une enum contenant des codes d’erreur qui sont passés au client mobile :

public enum ErrorCode
{
    TodoItemNameAndNotesRequired,
    TodoItemIDInUse,
    RecordNotFound,
    CouldNotCreateItem,
    CouldNotUpdateItem,
    CouldNotDeleteItem
}

Dans le terminal, testez l’ajout de nouveaux éléments en appelant la commande curl suivante à l’aide du verbe POST et en fournissant le nouvel objet au format JSON dans le corps de la requête.

curl -v -X POST 'http://localhost:5000/api/todoitems/' \
--header 'Content-Type: application/json' \
--data '{
  "id": "6bb8b868-dba1-4f1a-93b7-24ebce87e243",
  "name": "A Test Item",
  "notes": "asdf",
  "done": false
}' | jq

La commande curl précédente inclut les options suivantes :

  • --header 'Content-Type: application/json' : définit l’en-tête Content-Type sur application/json, indiquant que le corps de la requête contient des données JSON.
  • --data '{...}' : envoie les données spécifiées dans le corps de la requête.

La méthode retourne l’élément qui vient d’être créé dans la réponse.

Mise à jour d’éléments

La modification des enregistrements est effectuée via des requêtes HTTP PUT. Outre cette modification, la méthode Edit est presque identique à Create. Si l’enregistrement n’est pas trouvé, l’action Edit retourne une réponse NotFound (404).

[HttpPut]
public IActionResult Edit([FromBody] TodoItem item)
{
    try
    {
        if (item == null || !ModelState.IsValid)
        {
            return BadRequest(ErrorCode.TodoItemNameAndNotesRequired.ToString());
        }
        var existingItem = _todoRepository.Find(item.ID);
        if (existingItem == null)
        {
            return NotFound(ErrorCode.RecordNotFound.ToString());
        }
        _todoRepository.Update(item);
    }
    catch (Exception)
    {
        return BadRequest(ErrorCode.CouldNotUpdateItem.ToString());
    }
    return NoContent();
}

Pour tester avec curl, changez le verbe en PUT. Spécifiez les données de l’objet mis à jour dans le corps de la requête.

curl -v -X PUT 'http://localhost:5000/api/todoitems/' \
--header 'Content-Type: application/json' \
--data '{
  "id": "6bb8b868-dba1-4f1a-93b7-24ebce87e243",
  "name": "A Test Item",
  "notes": "asdf",
  "done": true
}' | jq

Cette méthode retourne une réponse NoContent (204) en cas de réussite, pour des raisons de cohérence avec l’API préexistante.

Suppression d’éléments

La suppression d’enregistrements est effectuée via des requêtes DELETE adressées au service, en passant l’ID de l’élément à supprimer. Comme pour les mises à jour, les requêtes pour des éléments qui n’existent pas reçoivent des réponses NotFound. Sinon, une requête qui réussit renvoie une réponse NoContent (204).

[HttpDelete("{id}")]
public IActionResult Delete(string id)
{
    try
    {
        var item = _todoRepository.Find(id);
        if (item == null)
        {
            return NotFound(ErrorCode.RecordNotFound.ToString());
        }
        _todoRepository.Delete(id);
    }
    catch (Exception)
    {
        return BadRequest(ErrorCode.CouldNotDeleteItem.ToString());
    }
    return NoContent();
}

Testez avec curl en modifiant le verbe HTTP en DELETE et en ajoutant l’ID de l’objet de données à supprimer à la fin de l’URL. Rien n’est requis dans le corps de la requête.

curl -v -X DELETE 'http://localhost:5000/api/todoitems/6bb8b868-dba1-4f1a-93b7-24ebce87e243'

Empêcher la sur-publication

Actuellement, l’exemple d’application expose l’ensemble de l’objet TodoItem. Les applications de production limitent généralement les données entrées et retournées à l’aide d’un sous-ensemble du modèle. Il y a plusieurs raisons à cela, et la sécurité en est une majeure. Le sous-ensemble d’un modèle est généralement appelé objet de transfert de données (DTO), modèle d’entrée ou modèle de vue. DTO est utilisé dans cet article.

Un DTO peut être utilisé pour :

  • Empêcher la sur-publication.
  • Masquer les propriétés que les clients ne sont pas censés voir.
  • Omettez certaines propriétés afin de réduire la taille de la charge utile.
  • Aplatir les graphes d’objets qui contiennent des objets imbriqués. Les graphiques d’objets aplatis peuvent être plus pratiques pour les clients.

Pour illustrer l’approche DTO, consultez Empêcher la sur-publication

Conventions des API web courantes

Quand vous développez des services backend pour votre application, vous souhaitez obtenir un ensemble cohérent de conventions ou de stratégies pour gérer les problèmes transversaux. Par exemple, dans le service montré précédemment, les requêtes pour des enregistrements spécifiques qui n’ont pas été trouvés ont reçu une réponse NotFound et non pas une réponse BadRequest. De même, les commandes envoyées à ce service qui ont passé des types liés au modèle ont toujours vérifié ModelState.IsValid et retourné un BadRequest pour les types de modèle non valide.

Une fois que vous avez identifié une stratégie commune pour vos API, vous pouvez en général l’encapsuler dans un filtre. Découvrez plus d’informations sur la façon d’encapsuler des stratégies d’API courantes dans les applications ASP.NET Core MVC.

Ressources supplémentaires