Accueil Nos publications Blog Un bol d’air frais avec Breeze.js

Un bol d’air frais avec Breeze.js

breezelogoVous l’avez sans doute constaté, les frameworks javascript ne manquent pas. Pourtant, quand nous avons besoin de récupérer et de gérer les données côté client, nous sommes parfois amenés à mixer l’utilisation de plusieurs librairies et écrire du “glue code”. Breeze.js, répond à certains de ces besoins tout en en offrant un nombre de fonctionnalités permettant d’écrire un code à la fois expressif et élégant.

Breeze.js permet entre autres de:

  • Interroger les données clientes ou serveur avec des requêtes inspirées par Linq
  • Gérer le cache client d’une façon transparente au développeur
  • Interagir facilement avec des services OData
  • L’utiliser facilement avec les frameworks MV* tels que Knockout et AngularJs

Ne tardons plus et allons découvrir Breeze.js

Si vous vous demandez comment ça marche, voici un exemple d’utilisation de Breeze.js:


    <script type="text/javascript">
    ...
    var conferenceQuery = breeze.EntityQuery.from("Presentations")
    .where("Theme", "==", "erlang")
    .orderBy("Date") .take(5);
    ...
    entityManager.executeQuery(presentation)
    .then(querySucceded)
    .fail(queryFailed);
    ...
    </script>
    

Avec ces quelques lignes de code, nous avons envoyé en asynchrone une requête au serveur permettant de récupérer les 5 premières conférences ayant comme thématique “erlang” et triés par date. Simple, clair et concis.

Une application cliente qui se base sur BreezeJs a besoin d’un service de persistance côté serveur. Par défaut, Breeze est livré avec les adapteurs pour ASP.NET Web API et OData. Il offre aussi les composants d’interface avec Entity Framework lui permettant de générer la “metadata Breeze” consommé par le client. Mais ce dernier n’a pas besoin de connaître la technologie serveur utilisée. Il peut être adapté pour communiquer avec n’importe quel service tant qu’il offre une API Json. Un implémentation du service en nodeJs/MangoDB est tout à fait possible.

Dans ce qui suit, je présente l’utilisation de Breeze avec Asp.Net Web API et Entity Framework 6. J’ai pris comme exemple l’implémentation d’une page basique de gestion de Conférences/Présentations. Commençons par récupérer les packages nécessaires avec Nuget:


    PM> Install-Package Breeze.WebApi2.EF6
    ...
    PM> Install-Package KnockoutJs
    ...
    

Attention: Plusieurs packages vont être installés dont Entity Framework 6 côté serveur et Q côté client. Nous allons avoir besoin de Knockout pour la partie MVVM/Data binding côté client.

Voici une vue d’ensemble, résumée, de l’application et des couches où nous allons mettre en oeuvre l’API Breeze: archiappbreezeJs

Côté serveur

Dans cet article, nous allons nous baser sur ce modèle. modelebreezejs C’est un modèle simple. Dans cet exemple, j’ai adopté une approche code first. Je ne vais pas m’attarder sur cette partie. Vous trouverez ici, le gist du dbcontext de l’exemple.

Afin que le client puisse parler “breeze” avec le serveur, nous devons définir un contrôleur Web API spécifique. Il doit exposer une méthode particulière fournissant les métadonnées du modèle au client qui prendra ainsi connaissance des entités exposées, de leur types et des relations établies entre elles. Cette métadonnée est Indispensable parce qu’elle permettra à BreezeJs de construire les requêtes, de mettre à jour et de créer des entités:


    namespace FunWithBreezeJs.Controllers
    {
    [BreezeController]
    public class CoolConfController : ApiController
    {
    private EFContextProvider _contextProvider = new EFContextProvider();

    [HttpGet]
    public string Metadata()
    {
    return _contextProvider.Metadata();
    }

    [HttpGet]
    public IQueryable Presentations()
    {
    return _contextProvider.Context.Presentations;
    }

    [HttpGet]
    public IQueryable Speakers()
    {
    return _contextProvider.Context.Conferenciers;
    }

    [HttpPost]
    public SaveResult SaveChanges(JObject modifications)
    {
    return _contextProvider.SaveChanges(modifications);
    }
    }
    }
    

Ici, l’utilisation de l’attribut BreezeControllerAttribute est indispensable. Il permet à la pipline Web API d’être configurée afin que ce contrôleur puisse fournir au client BreezeJS le format attendu des requêtes HTTP et des données. EFContextProvider est un classe de l’API Breeze.NET, elle est un wrapper de la classe data context CoolConfContext. Elle facilite l’interaction entre le Contrôleur Breeze et EntityFramewok, dans ce cas, elle fournira pour nous les métadonnées pour CoolConfContext ainsi que la mécanique qui permet de sauvegarder les modifications dans la base. Notez que les actions exposant les entités retournent IQueryable et non pas les données. Le client envoie, en effet, les requêtes avec la synatxe OData, Web API se charge de l’exécuter après un éventuel enrichissement(filtre, tri, etc.).

Une nouvelle route doit bien sûr être définie pour prendre en compte ce contrôleur. Nuget le fait quand nous installation le package de Breeze Web API: breezewebapiconfig Il va, en effet, ajouter automatiquement un WebActivator qui va définir cette nouvelle route avant l’appel de Application_Start dans Global.asax.
Nous pouvons également définir cette route manuellement dans la classe WebApiConfig à condition qu’elle définisse ce template de route “controller/action“. Un contrôleur Breeze s’attend toujours à avoir action mappé à ses méthodes:


    public static class WebApiConfig
    {
    public static void Register(HttpConfiguration config)
    {
    config.Routes.MapHttpRoute(name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional });
    ...
    config.Routes.MapHttpRoute(name: "BreezeApiRoute",
    routeTemplate: "breezeApi/{controller}/action",
    defaults: new { id = RouteParameter.Optional });
    ...
    }
    }
    

Intéressons nous maintenant à la vue cliente

Côté client

Un ensemble de scripts doivent être déclarés la page



    <script type="text/javascript" src="/Scripts/jquery-2.0.3.min.js"></script>
    <script type="text/javascript" src="/Scripts/knockout-3.0.0.js"></script>
    <script type="text/javascript" src="/Scripts/q.min.js"></script>
    <script type="text/javascript" src="/Scripts/breeze.min.js"></script>

    

Le client aura besoin de knockout.js, Q.js, jquery et évidemment de breeze.js.

Knockout permet d’implémenter le pattern MVVM en javascript. Il repose sur le binding entre la vue et le modèle de la vue (View Model). La vue est maintenue mise à jour grâces aux propriétés observables du View Model.

Q de son côté va permettre de définir des promises. Ce sont des objets représentant un résultat attendu d’une opération asynchrone. Breeze.js se base sur Q pour gérer les requêtes en asynchrone au serveur. Définissons le View Model de l’exemple:


    <script language="text/javascript">
    var em = new breeze.EntityManager("breeze/coolconf");

    var speakerVM = function (name, image, bio, presentations) {
    var self = this;

    self.name = ko.observable(name || '');
    self.image = ko.observable(image || '/images/defaultspeaker.png')
    self.bio = ko.observable(bio || '');
    self.presentations = ko.observableArray(presentations || []);
    };

    var speakersVM = function () {
    self = this;

    self.pageNumber = 0;
    self.query = ko.observable('');
    self.speakers = ko.observableArray([]);
    self.load = function (pageNum) {
    pageNum = pageNum || 0;
    var q = new breeze.EntityQuery
    .from("Speakers")
    .expand("Presentations");

    em.executeQuery(q).then(function (data) {
    self.speakers.removeAll();
    $.each(data.results, function (i, confDto) {
    var presentations = confDto.Presentations();
    self.speakers.push(new speakerVM(confDto.Name(), confDto.Image(), confDto.Bio(), presentations));
    });
    }).fail(function (err) {
    alert(err);
    });
    }
    }

    var root = new speakersVM();
    ko.applyBindings(root);
    root.load();

    </script>
    

La vue relative à ce view model:


    <ul data-bind="foreach: speakers">
    <li>
    <div>
    <div>
    <img alt="" data-bind="attr: {src : image}" />
    </div>
    <p>
    Nom: <span data-bind="text: name"></span>
    </p>
    <p>
    Biographie:<span data-bind="text: bio"></span>
    </p>
    <div>
    Présentations:
    <ul data-bind="foreach: presentations">
    <li>
    <p>
    titre:<span data-bind="text: Title"></span>
    </p>
    </li>
    </ul>
    </div>
    </div>
    </li>
    </ul>
    

La première ligne du script initialise le client BreezeJs en créant une nouvelle instance de Entitymanager pointant vers le contrôleur breeze/coolconf que nous avons défini plus haut. Quand la page est chargée, BreezeJs envoie une première requête pour récupérer les métadonnées du serveur: metadata Le serveur va ainsi répondre avec les métadonnées relatives au modèle: metadatareponse Récupérer la liste des speakers et de leur présentations est très simple. Nous devons tout d’abord “déclarer” la requête et puis l’exécuter via entitymanager. Vous remarquerez que la requête est très inspirée par Linq. Nous pouvons par exemple appliquer des filtres, paginer, ordonner et récupérer les entités connexes (avec l’utilisation d'”expand” dans cet exemple). BreezeJs exécute la requête en asynchrone. Quand le résultat est retourné, le bloc contenu dans then sera alors appelé. Dans le cas d’erreur, c’est le bloc contenu dans fail qui sera exécuté. La requête envoyée dans cet exemple correspond à cette URL: https://breeze/coolconf/Speakers?$expand=Presentations. Elle correspond à une URL OData, BreezeJs traduit les requêtes vers des URL OData capables d’être prises en compte par les contrôleurs Web API.

Voici un exemple de requêtes capables d’être gérées par Breeze


    //pagination. inlineCount renvoie le nombre de lignes avant l'application de skip() et take()
    var q = new breeze.EntityQuery.from("Speakers")
    .expand("Presentations")
    .skip(page * itemPerPage)
    .take(itemPerPage).inlineCount();

    //projection
    var q = new breeze.EntityQuery.from("Speakers")
    .select("name, bio");

    //conditions
    var q = new breeze.EntityQuery
    .from("Speakers")
    .where("name", "startsWith", "Martin");

    // tous les speakers qui ont des présentations dont le résumé contient SPA
    var q = new breeze.EntityQuery
    .from("Speakers")
    .expand("Presentations")
    .where("Presentations.Summary", "contains", "SPA");

    //utilisation de prédicats
    var p1 = new Predicate("Theme" , "==", "erlang");
    var p2 = new Predicate("Summary", "contains", "concurrency");
    var q = new breeze.EntityQuery
    .from("Presentations")
    .where(p1.and(p2));

    //tri sur plusieurs propriétés
    var q = new breeze.EntityQuery
    .from("Presentations")
    .orderBy("Date desc, Title");

    

Inutile de détailler ces requêtes. Comme vous avez dû le constater, les requêtes sont expressives et faciles à construire.

Pour aller plus loin

Breeze est une excellente libraire pour accéder aux données. Elle réduit considérablement la complexité des clients Javascript. Mais ce n’est pas tout, des fonctionnalités telles que le tracking des changements et la mise à jour des données n’ont pas été abordées dans cet article ; je vous invite évidemment à découvrir plus en détails ces fonctionnalités. Voici la documentation dans le site officiel.