Création d’une Web Api Rest avec ASP.net Core MVC

1. Objectif

Créer une Web API Rest permettant de créer, modifier, supprimer et consulter des données en JSON. Ce type de service s’inscrit totalement dans les architectures SOA qui ont la vertu d’être évolutive (scalable). En effet, imaginez une application composé d’une multitude de micro service, il est alors possible de les héberger sur différentes machines, voir même des fermes de machines.

Les Web API sont également par définition simple. Le fait qu’elle soit en REST (Representational State Transfer), contrairement à SOAP, signifie qu’elles reposent sur une couche protocole simple (rien d’autre qu’HTTP), qu’elles ne maintiennent pas d’états, son facilement mises en cache, et communique des informations généralement via Json qui est devenu un standard dans la communication Machine – Machine, ayant supplanté XML qui lui était plus verbeux (et donc d’avantage destiné à une compréhension humaine).

Les Web API sont par excellence les composants simples d’une application légère type smartphone ou site web mono page sollicitant différents composants pour établir son contenu.

Au final, nous ferons en sorte que notre Web Api soit hébergé de façon pérenne sur notre Raspberry Pi.

 

2. Pré requis

  • Avoir configuré correctement son environnement de travail que ce soit sur son ordinateur ainsi que sur le raspberry (Accès SSH, VNC, …).
  • Connaitre les bases du C# et de la programmation ASP.net MVC.
  • Avoir réalisé le tutoriel Créer un site Web .Net Core Razor MVC hébergé sur Raspbian. Cela permettra de connaitre les commandes pour créer la base, … Nous n’y reviendrons pas ici.

 

3. Création de la Web Api

Quitte à prendre un exemple, autant que ce soit un exemple qui pourra servir. J’ai pour projet de réaliser une solution domotique. Cette solution comprendra un serveur Raspberry Pi ainsi que des composants qui seront connectés via différents protocoles de communications. Réalisons donc une web Api permettant de gérer les éléments liés au composants et à leur connexion au réseau.

  A. Données manipulées

Voici une représentation synthétique des données manipulées :

Component: Composant caractérisé par

  • Id = Un identifiant
  • Name = Nom du composant
  • NetworkId = Son identifiant sur le réseau qui sera attribué par le serveur.
  • IsConnected = Booléen permettant de savoir s’il est connecté au serveur.
  • Type = Type de composant

ComponentType:

  • Code = Un identifiant
  • Name = Nom du type
  • Description = Description du type

Nous y retrouverons les Actionneurs, les capteurs ou encore les transducteurs.

NetworkComType:

  • Id = Identifiant du type de communication
  • Name = Nom du type de communication

Nous y retrouverons WiredI2c, WiredSerial, Wifi, Bluetooth, WaveXbee, WaveZwave, WaveNrf24 et Wave433Mhz

ComponentNetwork:

  • ComponentId : Identifiant du composant
  • NetworkComTypeId : Identifiant du moyen de communication
  • IsLeaf : S’agit il d’une feuille ou d’un noeud du moyen de communication ?

 

  B. Création de la WebApi et la partie Modèle

Lancez visual studio et créez un nouveau projet. Il s’agit qu’une application Web ASP.NET Core que nous nommerons WebApiComponent.

Dans le second écran, choisissez Api Web. L’application ainsi créé est rudimentaire.

    1. Petite explication sur le contenu de cette WebApi

Vous pouvez y voir dans startup.cs qu’il s’agit d’une application MVC. Il existe par défaut un répertoire controllers contenant un contrôleur de base.

Par convention un contrôleur possède un nom ayant pour suffix Controller. Le nom par défaut correspond au chemin dans l’url.

Vous noterez la présence dans le contrôleur ValuesController d’attributs (Ce sont des ‘directives’ en entête de class, de méthodes, de propriétés au même titre que celle que nous avons positionné pour le modèle).

Ces attributs permettent de définir ici les liens avec les requêtes HTTP : Chemin et méthodes de requêtes type GET, PUT, … Vous voyez devant le nom de class du contrôleur la route [Route(« api/[controller] »)] qui permet de définir le chemin d’accès HTTP à notre contrôleur ainsi que les différentes méthodes avec des HttpGet, … Nous y reviendrons plus tard.

Il nous faut maintenant créer nos entités dans une partie modèle. Par convention et définition, une entité est une classe ne possédant que des propriétés avec des Getteur et Setteur. Elles sont une représentation objet de nos données en base. C’est Entity Framework qui fera le travail d’alimenter un context (qui définie plusieurs collections d’entités (voir sa définition plus loin dans le tuto) avec les éléments en base. Il se chargera de toutes les requêtes SQL).

Nous allons donc maintenant rajouter notre modèle.

    2. Création du modèle

Nous souhaitons pour nos différentes entités ajouter une date de création et de modification. Ces champs seront implémenté dans les class entité sur la base d’une interface. Il est possible avec Entity Framework de réaliser un héritage de class, mais cela nécessite que la clé soit présente dans la class mère. Dans notre cas, nous avons une entité ayant pour clé un code sur trois lettre. Nous n’utiliserons donc qu’une Interface.

      a. Création des entités

Faite un clic droit sur le projet>Ajouter>répertoire>nommé le Models (qui par convention représente le répertoire contenant les données).

Y créer l’interface suivante :

IEntity.cs
using System;

namespace WebApiComponent.Models
{
    public interface IEntity
    {
        DateTime Created { get; set; }

        DateTime LastModified { get; set; }
    }
}

Créez maintenant les class suivantes :

Component.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace WebApiComponent.Models
{
    public class Component : IEntity
    {
        public uint Id { get; set; }
        [Required]
        public string Name { get; set; }

        public ushort? NetworkId { get; set; }

        [Required]
        public bool IsConnected { get; set; }

        [Required]
        public string ComponentTypeCode { get; set; }
        [ForeignKey("ComponentTypeCode")]
        public ComponentType Type { get; set; }

        public ICollection<ComponentNetwork> ComponentNetworkList { get; set; }
        public DateTime Created { get; set; }
        public DateTime LastModified { get; set; }
    }
}

Vous pouvez voir que dans cette entité, on retrouve des clés étrangères mais également d’autres entités, soit simplement une entité comme ComponentType ou un collection pour ComponentNetworkList.

Les clés permettent de définir les clés étrangères en base. Les Entités permettent de charger dans l’entité des informations connexes. Nous verrons comment plus tard.

Vous voyez également la présence d’attributs, ici des annotations (documentation). On souligne ici qu’un champ est requis mais surtout que pour une entité la clé étrangère et l’attribut défini juste avant.

ComponentNetwork.cs
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace WebApiComponent.Models
{
    public class ComponentNetwork : IEntity
    {
        [Required]
        public uint ComponentId { get; set; }
        [ForeignKey("ComponentId")]
        public Component Component { get; set; }

        [Required]
        public int NetworkComTypeId { get; set; }
        [ForeignKey("NetworkComTypeId")]
        public NetworkComType CommunicationType { get; set; }

        [Required]
        public bool isLeaf { get; set; }

        public DateTime Created { get; set; }
        public DateTime LastModified { get; set; }

    }
}
ComponentType.cs
using System;
using System.ComponentModel.DataAnnotations;

namespace WebApiComponent.Models
{
    public class ComponentType : IEntity
    {
        [Key, MinLength(3), MaxLength(3)]
        public string Code { get; set; }

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

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

        public DateTime Created { get; set; }
        public DateTime LastModified { get; set; }
    }
}
NetworkComType.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace WebApiComponent.Models
{
    public class NetworkComType : IEntity
    {
        public int Id { get; set; }

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

        public ICollection<ComponentNetwork> ComponentNetworkList { get; set; }

        public DateTime Created { get; set; }
        public DateTime LastModified { get; set; }
    }
}

Voilà pour les entités représentant les données manipulées. Passons maintenant au context Entity Framework

      b. Création du context Entity Framework

Le context Entity Framework va permettre de définir la liste des collections d’entités mappées avec la base de données. Il s’agit d’un objet qui hérite de DbContext. On y retrouvera également dans une méthode OnModelCreating, l’appel à des méthodes permettant de compléter les annotations précédemment mises sur nos entités. En effet, les attributs on l’avantage de donner de la visibilité mais ne permettent pas de tout définir. Il est alors possible de définir les directive de création de base de données via ce que l’on appel Code Fluent (Attention, on retrouve les mêmes concepts que pour les attributs, il faut donc veiller à ne pas être contradictoire car il s’agit en fait de la même chose : Paramétrer la création de la base).

Ici nous définirons la création de valeurs par défaut mais également les liens entre entités ou encore le fait qu’un identifiant est en auto incrément. Pour donner la lisibilité au code, je regroupe mes traitements par entité dans des régions.

ApiDataContext.cs
using Microsoft.EntityFrameworkCore;

namespace WebApiComponent.Models
{
    public class ApiDataContext : DbContext
    {
        public ApiDataContext(DbContextOptions<ApiDataContext> options)
                : base(options)
        {
        }

        public DbSet<Component> Components { get; set; }
       
        public DbSet<ComponentType> ComponentTypes { get; set; }

        public DbSet<NetworkComType> NetworkCommunicationTypes { get; set; }

        public DbSet<ComponentNetwork> ComponentsInNetwork { get; set; }

        protected override void OnModelCreating(ModelBuilder p_modelBuilder)
        {
            #region Component
            // Autoincrément pour la clé
            p_modelBuilder.Entity<Component>()
                            .Property(f => f.Id)
                            .ValueGeneratedOnAdd();

            // Valeurs par défaut
            p_modelBuilder.Entity<Component>()
                            .Property(e => e.Created)
                            .HasDefaultValueSql("now()");
            p_modelBuilder.Entity<Component>()
                            .Property(e => e.LastModified)
                            .HasDefaultValueSql("now()");           

            #endregion

            #region ComponentType
            // Valeurs par défaut
            p_modelBuilder.Entity<ComponentType>()
                            .Property(e => e.Created)
                            .HasDefaultValueSql("now()");
            p_modelBuilder.Entity<ComponentType>()
                            .Property(e => e.LastModified)
                            .HasDefaultValueSql("now()");
            #endregion

            #region NetworkComType
            // Autoincrément pour la clé
            p_modelBuilder.Entity<NetworkComType>()
                            .Property(f => f.Id)
                            .ValueGeneratedOnAdd();

            // Valeurs par défaut
            p_modelBuilder.Entity<NetworkComType>()
                            .Property(e => e.Created)
                            .HasDefaultValueSql("now()");
            p_modelBuilder.Entity<NetworkComType>()
                            .Property(e => e.LastModified)
                            .HasDefaultValueSql("now()");
            #endregion

            #region ComponentNetwork
            p_modelBuilder.Entity<ComponentNetwork>()
                            .HasKey(keys => new { keys.ComponentId, keys.NetworkComTypeId });

            p_modelBuilder.Entity<ComponentNetwork>()
                            .HasOne(cn => cn.Component)
                            .WithMany(c => c.ComponentNetworkList)
                            .HasForeignKey(cn => cn.ComponentId);

            p_modelBuilder.Entity<ComponentNetwork>()
                            .HasOne(cn => cn.CommunicationType)
                            .WithMany(n=> n.ComponentNetworkList)
                            .HasForeignKey(cn => cn.NetworkComTypeId);

            // Valeurs par défaut
            p_modelBuilder.Entity<ComponentNetwork>()
                            .Property(e => e.Created)
                            .HasDefaultValueSql("now()");
            p_modelBuilder.Entity<ComponentNetwork>()
                            .Property(e => e.LastModified)
                            .HasDefaultValueSql("now()");
            #endregion


        }
    }
}

Je vais ici expliquer une chose qui n’est peut être pas évidente à comprendre à la lecture du code. Notre table ComponentNetwork sera une table d’association entre Component et NetworkComType. Cela veut donc dire qu’il y a deux clés étrangères vers chacune de ces tables. On y retrouve également les objets Component et NetworkComType. Dans ces deux entités, on retrouve par conséquent une collection de ComponentNetwork puisque nous sommes dans une association n-ère.

C’est ce que décrit le code suivant :

p_modelBuilder.Entity<ComponentNetwork>()
 .HasKey(keys => new { keys.ComponentId, keys.NetworkComTypeId });

p_modelBuilder.Entity<ComponentNetwork>()
 .HasOne(cn => cn.Component)
 .WithMany(c => c.ComponentNetworkList)
 .HasForeignKey(cn => cn.ComponentId);

p_modelBuilder.Entity<ComponentNetwork>()
 .HasOne(cn => cn.CommunicationType)
 .WithMany(n=> n.ComponentNetworkList)
 .HasForeignKey(cn => cn.NetworkComTypeId);

La clé de la table est double. Ensuite prenons l’exemple pour l’association avec le Component. On explique que pour 1 Component ici présent (HasOne dans la table ComponentNetwork), il y a une collection (WithMany) de ComponentNetwork liés par la clé ComponentId.

    3. Création de la base de données

Nous utilisons une base de données MariaDb sur le Raspberry. Nous devons donc créer la base, configurer les droits et rajouter à notre projet le package Nuget permettant de s’interfacer avec les bases de données MariaDb.

Créez la base de données via Workbench ou via S.S.H. (voir tuto cité au besoin).

Ensuite installez le package Pomelo Entity Framework MySql et Design.

Dans le projet, ouvrer le fichier appsettings.json et rajoutez la chaine de connection tout en remplaçant les valeurs.

appsettings.json
{
  "Logging": {
    "IncludeScopes": false,
    "Debug": {
      "LogLevel": {
        "Default": "Warning"
      }
    },
    "Console": {
      "LogLevel": {
        "Default": "Warning"
      }
    }
  },
  "ConnectionStrings": {
    "DefaultConnection": "Server=IpRasberry;Database=NomMaBase;Uid=NomUser;Pwd=MotDePasse;"
  }
}

Dans le fichier Startup.cs, nous devons maintenant remplacer l’appel au DbContext comme ceci :

public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            services.AddDbContext<ApiDataContext>(options =>
                options.UseMySql(Configuration.GetConnectionString("DefaultConnection")));
        }

Voilà, maintenant notre projet est prêt. Reste à créer le modèle de données. Rien de plus simple avec Entity Framework code first !

Allez dans la console Nuget. On va y créer un point de migration. Comme c’est le premier, par convention, nous le nommons Initial.

Add-Migration Initial

on modifie maintenant la base de données

update-database

Et voilà, vous pouvez aller voir sur Workbench, le modèle a été créé.

 

  C. Définition des méthodes du contrôleur

Nous allons maintenant générer les contrôleurs permettant de réaliser un C.R.U.D. (Create, Read, Update, Delete) sur chacune de nos entités.

Faite un clic droit sur le répertoire Controllers>Ajouter>Contrôleur.

On va choisir un contrôleur d’API avec Actions utilisant Entity Framework

Une seconde fenêtre apparaît ou il faut choisir le modèle que l’on souhaite mapper ainsi que le contexte Entity Framework. Ainsi, le lien sera fait entre le contrôleur qui implémente un CRUD et la base de données via le contexte Entity Framework pour alimenter notre Entité dans le modèle.

Allez voir le contrôleur ainsi créé. Vous notez la présence de la définition de notre context Entity Framework en tant que membre de la class ainsi que les attributs permettant de définir les routes Http.

Il y a également [Produces(« application/json »)] qui stipule que les données échangés seront de type Json (content-type dans les pages web). De base, nous possédons les éléments suivants pour réaliser notre CRUD :

  • Un Post permettant d’ajouter un nouvel élément (CREATE)
  • Deux Get, un sans valeur pour obtenir la liste des éléments, ainsi qu’un permettant de stipuler l’identifiant (READ)
  • Un Put, permettant de réaliser des modifications (UPDATE)
  • Un Delete permettant de réaliser des suppressions (DELETE)

De la même façon, rajoutez maintenant les contrôleurs pour les autres modèles implémentés. C’est presque magique, notre WebApi est ainsi prête et nous pouvons maintenant la lancer.

Mais comment la tester ! Et bien nous allons utiliser un utilitaire bien pratique.

Lorsque le navigateur sera chargé. Copiez l’url.

 

  D. Test et utilisation de Postman

Notre Web Api utilise des méthodes de requêtes pour interagir avec la base de données via Entity Framework. Vous pouvez voir ces méthodes dans les contrôleurs comme nous l’avons déjà évoqué. On y retrouve GET, POST, PUT et DELETE.

Postman est un outil développé par Google qui permet d’interagir facilement avec notre API. Il possède de nombreuses fonctionnalités, le tout avec une interface graphique sympathique. Il existe sous forme d’application ou encore de plugin sous Chrome.

 

 

Installez Postman qui vous trouverez ici, puis lancez le.

 

Fermez la première fenêtre puis collez dans l’url le chemin de votre WebApi.

Rajoutez /api/ComponentTypes. A gauche, choisissez POST.

Cliquez sur Body, raw et choisissez JSON (et non le text par défaut) comme type de données.

Dans la fenêtre au dessous tapez :

{
"Code":"CPT",
"Name":"Capteurs",
"Description":"Dispositif transformant l'état d'une grandeur physique observée en une grandeur utilisable, telle qu'une tension électrique, une hauteur de mercure, une intensité ou la déviation d'une aiguille. Exemple : Sonde de température, de pression, capteur de présence."
}

Appuyez sur send. Vous pouvez observer la réponse qui comporte en plus nos champs par défaut de création et de modification.

Rajoutez de même pour les autres contrôleurs des élements.

api/NetworkComTypes

{
"Name":"WiredI2c"
}

Vous pouvez ici en rajouter un second, il nous servira.

Pour api/Components

{
"Name":"Thermomètre extérieur",
"IsConnected":false,
"ComponentTypeCode":"CPT"
}

et enfin pour api/ComponentNetworks les deux post suivant (a séparer lors de l’appel)

{
        "componentId": 1,
        "networkComTypeId": 1,
        "isLeaf": false
    }
    {
        "componentId": 1,
        "networkComTypeId": 7,
        "isLeaf": true
    }

Maintenant, passez sur la commande GET des Components. Appuyez sur Send.

 

On remarque que le type et le ComponentNetworkList sont null. Il s’agit d’entités plus complexes et notamment lié à des clés étrangères. Pour pouvoir avoir les Json correspondant, il faut indiquer au context Entity Framework de ramener des données connexe (infos ici). Nous allons voir comment faire ceci.

Vous pouvez arrêter votre application.

    E. Chargement des données connexes

Nous allons donc devoir indiquer au context Entity Framework qu’il faut charger les données connexes. Il faut noter plusieurs choses importantes. Certes, il est confortable de pouvoir charger des entités, dans des entités mais ceci s’avère coûteux en charge. Il faut le garder à l’esprit.

Il faut également limiter le niveau de chargement car dans notre cas, nous pouvons observer que si nous chargeons la liste des ComponentNetworks, ils peuvent contenir des Component qui eux mêmes comportent des ComponentNetworksList et ainsi suite. Ce qui reviendrait à charger en boucle les données.

Nous allons donc dans les contrôleurs inclure 1 voir 2 niveaux de lecture des données connexes.

Avant cela, nous devons préciser au chargement MVC une option Json concernant la sérialisation des données.

Chargez startup.cs, dans le chargement du service MVC, rajoutez l’option suivante.

startup.cs
services.AddMvc()
                    .AddJsonOptions(options =>
                            options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);

Nous allons maintenant à titre d’exemple, charger les données connexes dans le contrôleur Component.

Ouvrez ComponentsController.cs.

Nous allons modifier les deux Get.

Pour le GetComponents, nous allons remplacer le return _context.Components; par ce qui suit :

// GET: api/Components
        [HttpGet]
        public IEnumerable<Component> GetComponents()
        {
            DbSet<Component> v_set = _context.Components;
            //Chargement des données connexes
            v_set.Include(c => c.Type).Load();
            v_set.Include(c => c.ComponentNetworkList).ToList();
            return v_set;
        }

Vous observez que l’on peut sur une même commande d’ensemble enchaîner les appels Include. Vous noterez que sur la première, j’ai réalisé un load et sur la seconde un ToList. Cela revient au même. Mais il est important de noter que pour les méthodes asynchrones, il exsite un ToListAsync ce qui n’est pas le cas pour le load.

Relancez votre application et voyons le résultat d’un Get dans Postman :

C’est pas mal du tout, nous avons maintenant le type renseigné et le tableau de ComponentNetwork. Nous observons néanmoins qu’il manque le communicationType à ce niveau.

On pourrait essayer à ce niveau d’utiliser un ThenInclude fourni par Entity Framework mais nous obtiendrions alors une erreur.

L’astuce consiste alors à passer par une requête Linq qui elle est plus permissive. Modifiez donc la méthode comme ceci :

[HttpGet]
        public IEnumerable<Component> GetComponents()
        {
            DbSet<Component> v_set = _context.Components;
            //Chargement des données connexes
            v_set.Include(c => c.Type).Load();
            var v_query = v_set.Include(c => c.ComponentNetworkList).AsQueryable();
            v_query.SelectMany(c => c.ComponentNetworkList)
                    .Include(w => w.CommunicationType)
                    .ToList();
            return v_query;
        }

Vous pouvez maintenant relancer l’application et constater le résultat dans Postman :

C’est parfait. Nous avons maintenant le niveau souhaité d’information.

Passons maintenant à la méthode Get d’un élément. Malheureusement, nous allons y perdre son côté asynchrone. Il nous faut la transformer ainsi :

// GET: api/Components/5
        [HttpGet("{id}")]
        public IActionResult GetComponent([FromRoute] uint id)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            DbSet<Component> v_set = _context.Components;
            //Chargement des données connexes
            v_set.Include(c => c.Type).Load();
            var v_query = v_set.Include(c => c.ComponentNetworkList).AsQueryable();
            v_query.SelectMany(c => c.ComponentNetworkList)
                    .Include(w => w.CommunicationType)
                    .ToList();

            var v_component = v_query.SingleOrDefaultAsync(m => m.Id == id).Result;

            if (v_component == null)
            {
                return NotFound();
            }

            return Ok(v_component);
        }

Vous obtiendrez un résultat équivalent que pour la list précédente. Je vous laisse faire de même pour les autres contrôleurs.

Nous avons maintenant une WebApi fonctionnelle et complète. Reste à l’héberger sur notre raspberry de façon définitive.

    F. Compilation pour le raspberry

Déchargez le projet et faite un clic droit sur le projet pour modifier le csproj.

Dans la partie PropertyGroup du XML rajoutez la balise RuntimeIdentifiers

 <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
    <RuntimeIdentifiers>win-x64;win10-x64;linux-arm</RuntimeIdentifiers>
  </PropertyGroup>

Rechargez le projet. Allez dans Générer > Publier

Choisissez un répertoire sur votre Raspberry  et créez un profil.

Dans les paramètres choisissez le mode release ainsi que linux-arm. (Pour plus de détail, voir le tutoriel cité en pré requis).

Aller hop, on publie notre WebApi sur le Raspberry Pi.

 

4. Installation sous un serveur Web

 

  A. Nginx

Nginx est un serveur web sans interface. Il nous servira de reverse proxy apportant son lot de fonctionnalités utiles au bon fonctionnement de nos applications web (notamment du cache). Pour faire simple, il sert de point d’entrée aux requêtes Http qui seront faite sur notre Raspberry Pi.

Pourquoi Nginx alors qu’Apache est très connu sous linux ? Et bien parce que Nginx est un serveur dit asynchrone capable d’exécuter plusieurs tâches simultanément. Cela fait de lui un serveur web particulièrement performant.

Il répond plus vite, il permet d’avantage d’utilisateurs simultanés, et il consomme moins de mémoire vive. Bien évidemment sur un Raspberry, on cherche la performance. Il n’en faut donc pas plus pour justifier ce choix.

    1. Installation

Sous une invite de commande tapez :

sudo apt-get install nginx

Nginx s’installe (validez la question). Une fois l’installation terminée,  Il faut maintenant lancer le service :

sudo service nginx start

Configurons maintenant la bête :).

     2. Configuration

Il nous faut maintenant configurer le serveur Nginx en tant que proxy inverse. Nous avons notamment remarqué que les sites avec Kestrel se lancent sur le port 5000. Nous allons router via Nginx les demandes du port 80 sur le port 5000.

sudo nano /etc/nginx/sites-available/default

Dans la partie server mettez les lignes suivantes, puis enregistrez et quittez nano.

server {
    listen 80;
    location / {
        proxy_pass http://localhost:5000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection keep-alive;
        proxy_set_header Host $http_host;
        proxy_cache_bypass $http_upgrade;
    }
}

Ici, nous voyons directement les effets de Nginx qui sert de reverse proxy : D’un côté, il écoute sur le port 80 les requêtes entrantes puis établi la jonction avec en local avec les trames http du port 5000 émise par Kestrel de dotnet Core.

lancez la commande suivante pour vérifier que le fichier de configuration est ok:

sudo nginx -t

Et comme tout est ok, on peut recharger la configuration

sudo nginx -s reload

Nginx est désormais configuré pour transférer les requêtes faites à http://yourhost:80 à l’application ASP.NET Core s’exécutant sur Kestrel à l’adresse http://127.0.0.1:5000

        a. Adaptation de l’application pour utiliser kestrel

Retournez sous Visual Studio et chargez le fichier program.cs. Nous allons rajouter l’appel à Kestrel :

public class Program
    {
        public static void Main(string[] args)
        {
            BuildWebHost(args).Run();
        }

        public static IWebHost BuildWebHost(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseKestrel()
                .UseStartup<Startup>()                
                .Build();
    }

Vous pouvez maintenant republier sur le raspberry.

Ensuite, allez dans le répertoire de votre application et lancez la :

sudo dotnet WebApiComponent.dll

Et maintenant, vous pouvez retourner sur Postman et remplacer localhost par l’IP de votre Raspberry. Et voilà, le tour est joué, Nginx nous sert maintenant de reverse proxy.

  B. Création d’un service pour notre WebApi

Maintenant, il faut faire en sorte que notre application soit lancée automatique tel un service au démarrage de Raspbian afin d’avoir une WebApi perpétuellement accessible.

On va créer un répertoire d’hébergement dans /var/AspNetCore/WebApiComponent

puis on va recopier les fichiers compilés ici

cp -r /chemin/Partage/WebApi/* /var/AspNetCore/WebApiComponent

maintenant créons le fichier de service

sudo nano /etc/systemd/system/kestrel-WebApiComponent.service

[Unit]
Description=WebApiComponent

[Service]
WorkingDirectory=/var/aspnetcore/WebApiComponent
ExecStart=/usr/local/bin/dotnet /var/AspNetCore/WebApiComponent/WebApiComponent.dll
Restart=always
RestartSec=10  # Restart service after 10 seconds if dotnet service crashes
SyslogIdentifier=dotnet-example
User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false

[Install]
WantedBy=multi-user.target

Enregistrez et quittez.

Nous voyons que l’utilisateur par défaut qui lance le service est www-data. Assez commun pour les applications web.

Nous allons nous assurer de sa présence :

cat /etc/passwd | awk -F: '{print $ 1}'


Si ce n’est pas le cas, il faudra le créer mais normalement il devrait être présent. Après la copie de vos fichiers, ils doivent certainement posséder les droits suivant:

Dans le répertoire /var/AspNetCore/WebApiComponent faite un

ls -l

vos fichiers ont certainement les droits avec le propriétaire root

-rwxr--r--

Nous allons définir comme propriétaire www-data

sudo chown -R www-data *

On va rajouter le droit d’exécution sur le service :

sudo chmod +x kestrel-WebApiComponent.service

Vous pouvez par la suite affiner les droits à votre guise. Des informations complémentaires sont disponibles ici, notamment pour sécuriser d’avantage votre serveur.
On démarre le service :

sudo systemctl start kestrel-WebApiComponent.service

Et enfin on peut vérifier son statut :

sudo systemctl status kestrel-WebApiComponent.service

On active le service lors du démarrage du raspberry :

sudo systemctl enable kestrel-WebApiComponent.service

Et voilà le service est fonctionnel et maintenant notre Web Api fonctionne définitivement.

5. Conclusion

Nous avons vu qu’il était très simple à l’aide d’Entity Framework Core et du modèle MVC de générer des Api Rest permettant d’échanger des données qui seront consommé par un site web par exemple. Dans un prochain tutoriel, nous verrons comment héberger plusieurs Api et un site les consommant. Si vous avez des commentaires, n’hésitez pas.

One thought on “Création d’une Web Api Rest avec ASP.net Core MVC

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.