Blazor : Simple CRUD App - HttpClient

Série d'article sur Blazor :

Jusqu'à présent, je ne vous ai présenté Blazor que côté client. Cette fois, nous allons voir un scénario plus complet qui consiste à interagir avec une WebAPI, donc côté serveur. Le gros avantage (de mon point de vue) c'est que comme côté serveur et côté client on a du code C# (.NET pour ne pas vexer les développeurs VB .NET, F# & co), on peut partager du code entre le client et le serveur.

Prenons l'exemple d'une application Angular qui consomme une WebAPI. Si la WebAPI retourne un objet Person définie dans la classe Person.cs, il faut que côté Angular, on définisse l'interface IPerson.ts avec les même propriétés.

Là, avec Blazor, comme la classe Person.cs sera partagée entre le serveur et le client, le problème de redondance d'information (et possibilité d'erreur) ne se pose plus.

Donc,

Lancez Visual Studio 2017 15.7 (preview) et créez un nouveau projet ASP .NET. Cette fois, dans les templates Blazor, choisissez l'option Blazor ASP .NET Core Hosted :

Cette fois, le template comprends 3 projets :

  • SimpleCrudBlazor.Client : l'application cliente WebAssembly,
  • SimpleCrudBlazor.Server : l'application serveur de WebAPI,
  • SimpleCrudBlazor.Shared : la dll avec des classes utilisées par le serveur ET le client.

Dans Shared, j'ai créé une classe Person bête et méchante. Moralité, dans mes pages exécutées côté client je peux instancier un objet Person tout comme dans mes controllers de mes WebAPI.

Dans Server, j'ai crée un controller qui me retourne la liste de mes clients via /api/Persons.

Dans Client, j'ai crée un service qui va appeler la WebApi /api/Persons.

Je passe sur l'implémentation du controller du serveur qui est tout ce qu'il y a de plus classique. Regardons plutôt notre classe PersonService dans le client :

using System;
using SimpleCrudBlazor.Shared;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Blazor;
 
namespace SimpleCrudBlazor.Client.Services
{
    public class PersonService
    {
        private readonly HttpClient _client;
        private readonly ConsoleService _consoleService;
        private readonly string _apiUrl = "/api/Persons";
 
        public PersonService(HttpClient client, ConsoleService consoleService)
        {
            _client = client;
            _consoleService = consoleService;
        }
 
        public async Task<IEnumerable<Person>> GetAllAsync()
        {
            _consoleService.Log(this"GetAllPersons");
            var persons = await _client.GetJsonAsync<Person[]>(_apiUrl);
            return persons;
        }
 
        public async Task<Person> UpdateAsync(Person person)
        {
            _consoleService.Log(this"UpdateAsync" + person.Id);
            return await _client.PutJsonAsync<Person>($"{_apiUrl}/{person.Id}", person);
        }
 
        public async Task<int> DeleteAsync(int personId)
        {
            throw new NotImplementedException();
        }
 
        public async Task<Person> Insert(Person person)
        {
            throw new NotImplementedException();
        }
    }
}

On voit que dans le constructeur de notre service, on attend 2 autres services (qui seront injectés via le moteur d'injection de dépendance). On a notre ConsoleService (cf article précédent) et surtout le HttpClient.

Le code est "classique" en .NET sauf qu'il y a un gros problème : ici, notre code doit faire un appel http vers une ressource. Or, faire un appel http depuis un navigateur, c'est interdit pour des raisons de sécurité. Seul le navigateur a le droit d'effectuer des requêtes Http.

Et c'est là ou l'on a une grosse astuce.

Le moteur d'injection de Blazor est un BrowserServiceProvider et ce dernier par défaut référence deux services, le IUriHelper (on s'en fou pour le moment) et un HttpClient avec un MessageHandler bien particulier. Ce messageHandler est un BrowserHttpMessageHandler qui intercepter donc tout appel avec cette classe.

Que fait-il ? Tout simplement (je dis ca mais c'est pas trivial du tout), il récupère l'appel "http .NET", la sérialise et fait un appel à une méthode javascript déjà enregistrée par le framework Blazor qui elle appelle la méthode fetch du navigateur. Vous avez compris l'astuce ?

  • Blazor par défaut enregistre une méthode javascript (Blazor.registerfunction) comme on a vu dans l'article précédent dont le nom est Microsoft.AspNetCore.Blazor.Browser.BrowserHttpMessageHandler.
  • Dans cette méthode javascript qui a plein d'arguments, il utilise la méthode fetch du navigateur pour faire l'appel vers la WebAPI,
  • Quand on utilise un HttpClient via le moteur d'injection, ce dernier a un handler,
  • Et ce handler appele la méthode javascript avec la méthode Invoke.

Cool, simple, efficace.

Moralité, c'est bien le navigateur qui fait l'appel Http et non l'implémentation initiale du HttpClient .NET.

Il ne reste plus qu'à utiliser le moteur d'injection de Blazor pour ajouter notre service PersonService et le rendre ainsi disponible pour toute notre application :

static void Main(string[] args)
        {
            var serviceProvider = new BrowserServiceProvider(configure =>
            {
                configure.Add(ServiceDescriptor.Singleton<PersonServicePersonService>());
                configure.Add(ServiceDescriptor.Singleton<ConsoleServiceConsoleService>());
            });
 
            new BrowserRenderer(serviceProvider).AddComponent<App>("app");
        }

blog comments powered by Disqus