Partager via


ASP.NET

Création d'API Web hypermédias avec l'API Web ASP.NET

Pablo Cibraro

Télécharger l'exemple de code

Hypermédia, mieux connu en tant qu'hypermédia comme moteur de l'état de l'application (HATEOAS), est une des principales contraintes de REST (Representational State Transfer). L'idée est que des artefacts hypermédias, tels que des liens ou des formulaires, peuvent être utilisés pour décrire de quelle manière des clients ont la possibilité d'interagir avec un ensemble de services HTTP. Ce concept a rapidement gagné en intérêt en matière de développement de conception d'API évolutive. Cela n'est en rien différent de notre mode d'interaction habituel avec le Web. Généralement, nous nous rappelons d'un point d'entrée unique ou d'une URL pour la page d'accueil d'un site Web avant de parcourir les différentes sections du site en utilisant des liens. Nous utilisons également des formulaires, fournis avec une action prédéfinie ou une URL, afin de soumettre des données dont le site peut avoir besoin pour réaliser une action spécifique.

Les développeurs ont tendance à fournir des descriptions statiques de toutes les méthodes prises en charge dans un service, allant de contrats formels tels que WSDL (Web Services Description Language) dans des services SOAP à une simple documentation dans des API Web non-hypermédias. Le principal problème à cet égard repose sur le fait qu'une description API statique engendre une corrélation étroite entre les clients et le serveur. En un mot, cela entrave l'évolutivité car toute modification de la description d'API peut être source de rupture avec tous les clients existants.

Pendant un temps, cela n'a pas posé de problème au niveau de l'entreprise où le nombre d'applications clientes pouvait être contrôlé et connu à l'avance. Cependant, lorsque le nombre de clients potentiels augmente de manière exponentielle, comme c'est le cas aujourd'hui, avec des milliers d'applications tierces s'exécutant sur plusieurs périphériques, cette idée est à bannir. Et pourtant, le simple fait de passer de services SOAP à HTTP ne constitue pas une garantie de résoudre ce problème. Si, par exemple, il existe une certaine connaissance sur le client pour le calcul d'URL, le problème persiste, même sans aucun contrat explicite tel que WSDL. C'est hypermédia qui permet de protéger les clients contre toutes modifications de serveur.  

Le flux de travail d'état de l'application, qui détermine ce que le client peut faire ensuite, devrait également se trouver côté serveur. Supposons qu'une action dans une ressource est disponible uniquement pour un état donné. Cette logique devrait-elle résider dans tout client de l'API possible ? En aucun cas. C'est toujours le serveur qui doit signifier ce qui est faisable avec une ressource. Par exemple, si un bon de commande est annulé, l'application cliente ne doit pas être autorisée à le soumettre, ce qui signifie qu'aucun lien ou formulaire permettant de soumettre ce bon de commande ne doit être disponible dans la réponse envoyée au client.

Hypermédia apporte la solution

La liaison a toujours été au cœur de l'architecture REST. Bien entendu, les liens sont courants dans des contextes d'IU tels que des navigateurs ; pensons, par exemple, à un lien « Consultez les détails » permettant d'obtenir des informations sur un produit donné dans un catalogue. Mais qu'en est-il des scénarios ordinateur à ordinateur sans IU ou interaction utilisateur ? L'idée est que vous pouvez utiliser des artefacts hypermédias dans ces scénarios également. 

Grâce à cette nouvelle approche, le serveur ne se contente pas de renvoyer des données. Il renvoie également des données et des artefacts hypermédias. Les artefacts hypermédias permettent au client de déterminer l'ensemble disponible d'actions pouvant être réalisées en un point donné, en fonction de l'état du flux de travail de l'application de serveur.

Il s'agit d'un domaine qui établit généralement une différence entre une API Web classique et une API RESTful, mais d'autres contraintes s'appliquent également et il n'est donc pas intéressant de chercher à savoir si une API est RESTful ou non dans la plupart des cas. L'important c'est que l'API utilise correctement HTTP en tant que protocole d'application et tire profit d'hypermédia lorsque cela est possible. En activant hypermédia, vous pouvez créer des API identifiées automatiquement. Cela ne constitue pas une excuse pour ne pas fournir de documentation, mais les API sont plus flexibles en termes de capacité de mise à jour.

La détermination des artefacts hypermédias disponibles se fonde principalement sur les types de médias choisis. Nombre des types de médias que nous utilisons actuellement pour le développement d'une API Web, par exemple JSON ou XML, ne sont pas dotés d'un concept intégré pour la représentation de liens ou de formulaires, comme c'est le cas pour le HTML. Vous pouvez tirer profit de ces types de médias en définissant une manière d'exprimer hypermédia, mais les clients doivent alors comprendre comment la sémantique hypermédia est définie avec ces types. En revanche, des types de médias tels que XHTML (application/xhtml+xml) ou ATOM (application/atom+xml) prennent déjà en charge une partie de ces artefacts hypermédias, par exemple des liens ou des formulaires.

Dans le cas du HTML, un lien consiste en trois composants : un attribut « href » indiquant une URL, un attribut « rel » permettant de décrire de quelle manière le lien est en relation avec la ressource actuelle et un attribut « type » en option permettant de préciser le type de média. Par exemple, si vous voulez présenter une liste de produits dans un catalogue en utilisant XHTML, la charge utile de ressource pourra ressembler à ce qui est présenté sur la figure 1.

Figure 1 Utilisation du XHTML pour présenter une liste de produits

    <div id="products">
      <ul class="all">
        <li>
          <span class="product-id">1</span>
          <span class="product-name">Product 1</span>
          <span class="product-price">5.34</span>
          <a rel="add-cart" href="/cart" type="application/xml"/>
        </li>
        <li>
          <span class="product-id">2</span>
          <span class="product-name">Product 2</span>
          <span class="product-price">10</span>
          <a rel="add-cart" href="/cart" type="application/xml"/>
        </li>
      </ul>
    </div>

Dans cet exemple, le catalogue de produits est représenté avec des éléments HTML standard, mais j'ai utilisé XHTML parce qu'il est bien plus simple de procéder à une analyse avec une bibliothèque XML existante. De même, dans le cadre de la charge utile, un élément d'ancrage (a) a été inclus. Il représente un lien pour l'ajout de l'article au panier actuel de l'utilisateur. En regardant le lien, un client peut déduire son utilisation via l'attribut rel (ajouter un nouvel article) et l'utilisation de href pour effectuer une opération sur cette ressource (/cart). Il est important de noter que les liens sont générés par le serveur en fonction de son flux de travail professionnel afin que le client ne doive pas coder en dur une URL ou déduire une règle. Cela offre également de nouvelles opportunités pour modifier le flux de travail pendant l'exécution sans affecter en rien les clients existants. Si l'un des produits proposés dans le catalogue n'est plus disponible, le serveur peut tout simplement omettre le lien permettant d'ajouter ce produit dans le panier. Du point de vue du client, le lien n'est pas disponible et le produit ne peut donc pas être commandé. Des règles plus complexes relatives à ce flux de travail peuvent s'appliquer du côté serveur. Mais alors le client n'en sait rien du tout, la seule chose qui compte étant que le lien est absent. Grâce à l'hypermédia et à des liens, le client a été dissocié du flux de travail professionnel du côté serveur.

De plus, l'évolutivité d'une conception d'API peut être améliorée grâce à l'hypermédia et des liens. À mesure que le flux de travail professionnel évolue sur le serveur, il peut proposer des liens supplémentaires pour une nouvelle fonctionnalité. Dans notre exemple de catalogue de produits, le serveur peut inclure un nouveau lien permettant de classer un produit comme favori, comme suit :

    <li>
      <span class="product-id">1</span>
      <span class="product-name">Product 1</span>
      <span class="product-price">5.34</span>
      <a rel="add-cart" href="/cart/1" type="application/xml"/>
      <a rel="favorite" href="/product_favorite/1" 
         type="application/xml"/>
    </li>

Si les clients existants peuvent ignorer ce lien et ne pas être affectés par cette nouvelle fonctionnalité, des clients plus récents peuvent commencer à l'utiliser immédiatement. De cette manière, il ne serait pas insensé de penser à avoir une URL racine ou un point d'entrée unique pour votre API Web contenant des liens permettant de découvrir le reste de la fonctionnalité. Par exemple, vous pourriez avoir une seule URL « /panier_achat » qui renvoie la représentation HTML suivante :

    <div class="root">
      <a rel="products" href="/products"/>
      <a rel="cart" href="/cart"/>
      <a rel="favorites" href="/product_favorite"/>
    </div>

Une fonctionnalité analogue est également disponible dans les services OData, ce qui affiche un document service unique dans l'URL racine avec tous les ensembles de ressources et liens pris en charge pour l'obtention des données qui y sont associées.

Les liens sont une excellente manière de connecter des serveurs et des clients, mais ils posent un problème évident. Dans l'exemple ci-avant du catalogue de produits, un lien dans HTML offre uniquement les attributs rel, href et type, ce qui implique une certaine connaissance hors plage de ce qu'il convient de faire avec cette URL exprimée dans l'attribut href. Le client devrait-il utiliser un HTTP POST ou HTTP GET ? S'il utilise un POST, quelles données le client inclurait-il dans le corps de la demande ? Si ces connaissances pouvaient être documentées quelque part, ne serait-il pas formidable que les clients puissent effectivement découvrir cette fonctionnalité ? Pour répondre à toutes ces questions, c'est le recours à des formulaires HTML qui s'impose sans hésitation.

Formulaires en action :

Lorsque vous interagissez avec le Web via un navigateur, des actions sont habituellement représentées avec des formulaires. Dans l'exemple du catalogue de produits, le fait d'actionner le lien Ajouter au panier implique l'envoi d'un HTTP GET au serveur pour renvoyer un formulaire HTML pouvant être utilisé pour ajouter le produit au panier. Ce formulaire pourrait contenir un attribut « action » avec une URL, un attribut « méthode » représentant la méthode HTTP et des champs de saisie pouvant nécessiter une entrée de la part de l'utilisateur, ainsi que quelques instructions lisibles pour avancer.

Vous pouvez procéder de même pour un scénario d'ordinateur à ordinateur. Au lieu d'avoir un humain interagissant avec un formulaire, vous pourriez disposer d'une application exécutant JavaScript ou C#. Dans le catalogue de produits, un HTTP GET sur le lien « Ajouter au panier » pour le premier produit permettrait de récupérer le formulaire suivant représenté en XHTML :

    <form action="/cart" method="POST">
      <input type="hidden" id="product-id">1</input>
      <input type="hidden" id="product-price">5.34</input>
      <input type="hidden" id="product-quantity" class="required">1</input>
      <input type="hidden" id="___forgeryToken">XXXXXXXX</input>
    </form>

L'application cliente a maintenant été découplée de certains détails en relation avec l'ajout du produit au panier. Il suffit de soumettre ce formulaire en utilisant un HTTP POST à l'URL spécifiée dans l'attribut action. Le serveur peut également inclure d'autres informations dans le formulaire, par exemple un jeton anti-contrefaçon pour éviter les attaques de contrefaçons de requêtes de site à site ou pour signer les données préremplies pour le serveur.

Ce modèle permet à n'importe quelle API Web d'évoluer librement en proposant de nouveaux formulaires basés sur différents facteurs tels que des autorisations utilisateur ou la version que le client veut utiliser.

Hypermédia pour XML et JSON ?

Comme je l'ai mentionné précédemment, les types de médias génériques pour XML (application/­xml) et JSON (application/json) ne disposent pas d'un support intégré pour des formulaires ou des liens hypermédias. S'il est possible d'étendre ces types de médias à des concepts spécifiques au domaine tels que « application/vnd-panier+xml », cela nécessite que les nouveaux clients comprennent toute la sémantique définie dans ce nouveau type (et cela générerait probablement également une prolifération des types de médias) et de ce fait, ça n'est généralement pas considéré comme étant une bonne idée.

Pour cette raison, un nouveau type de média étendant XML et JSON avec une sémantique de liaison et qui est appelé HAL (Hypertext Application Language) a été proposé. Le projet, qui définit simplement une manière standard d'exprimer des liens hypertexte et ressources intégrées (données) en utilisant XML et JSON, est disponible à l'adresse suivante : stateless.co/hal_specification.html. Le type de média HAL définit une ressource qui contient un ensemble de propriétés, en jeu de liens et un groupe de ressources incorporées, comme illustré à la Figure 2.

The HAL Media Type
Figure 2 Le type de média HAL

La figure 3 illustre un exemple de l'aspect qu'aurait un catalogue de produits dans HAL en utilisant les deux représentations : XML et JSON. La figure 4 correspond à la représentation JSON de l'exemple de ressource.

Figure 3 Le catalogue de produits dans HAL

    <resource href="/products">
      <link rel="next" href="/products?page=2" />
      <link rel="find" href="/products{?id}" templated="true" />
      <resource rel="product" href="/products/1">
        <link rel="add-cart" href="/cart/" />
        <name>Product 1</name>
        <price>5.34</price>
      </resource>
      <resource rel="product" href="/products/2">
        <link rel="add-cart" href="/cart/" />
        <name>Product 2</name>
        <price>10</price>
      </resource>
    </resource>

Figure 4 La représentation JSON de l'exemple de ressource

{
  "_links": {
    "self": { "href": "/products" },
    "next": { "href": "/products?page=2" },
    "find": { "href": "/products{?id}", "templated": true }
  },
  "_embedded": {
    "products": [{
      "_links": {
        "self": { "href": "/products/1" },
        "add-cart": { "href": "/cart/" },
      },
      "name": "Product 1",
      "price": 5.34,
    },{
      "_links": {
        "self": { "href": "/products/2" },
        "add-cart": { "href": "/cart/" }
      },
      "name": "Product 2",
      "price": 10
    }]
  }
}

Prise en charge de hypermédia dans l'API Web ASP.NET

Jusqu'à présent, j'ai abordé partiellement la théorie sous-jacente de hypermédia dans la conception d'API Web. Découvrons à présent de quelle manière cette théorie peut être effectivement mise en place concrètement en utilisant l'API Web ASP.NET, avec tous les points et fonctions d'extension que cet environnement fournit.

Au niveau de base, l'API Web ASP.NET prend en charge l'idée de formateurs. Une mise en place formateur sait comment gérer un type de média spécifique, et de quelle manière sérialiser ou désérialiser vers des types .NET concrets. Par le passé, la prise en charge de nouveaux types de médias était très limitée dans ASP.NET MVC. Seuls HTML et JSON étaient traités comme des citoyens de premier ordre et entièrement pris en charge sur l'ensemble de la pile. De plus, il n'y avait aucun modèle cohérent pour la prise en charge d'une négociation de contenu. Vous pouviez prendre en charge différents formats de types de médias pour les messages de réponse en fournissant des implémentations ActionResult personnalisées, mais il n'était pas établi clairement de quelle manière un nouveau type de média pourrait être introduit pour la désérialisation de messages de demande. Cela était généralement résolu en tirant profit de l'infrastructure de liaison de modèle avec de nouveaux binders de modèles ou fournisseurs de valeurs. Cette incohérence a heureusement été résolue dans L'API Web ASP.NET avec l'introduction de formateurs.

Chaque formateur est issu du System.Net.Http.Formatting.MediaTypeFormatter de classe de base et remplace par un format de type de média donné la méthode CanReadType/ReadFromStreamAsync pour la prise en charge de la désérialisation et la méthode CanWriteType/WriteToStreamAsync pour la prise en charge de la sérialisation de types .NET.

La figure 5 présente la définition de la classe MediaTypeFormatter.

Figure 5 La classe MediaTypeFormatter

public abstract class MediaTypeFormatter
{
  public Collection<Encoding> SupportedEncodings { get; }
  public Collection<MediaTypeHeaderValue> SupportedMediaTypes { get; }
  public abstract bool CanReadType(Type type);
  public abstract bool CanWriteType(Type type);
  public virtual Task<object> ReadFromStreamAsync(Type type, 
    Stream readStream,
    HttpContent content, IFormatterLogger formatterLogger);
  public virtual Task WriteToStreamAsync(Type type, object value,
    Stream writeStream, HttpContent content, 
    TransportContext transportContext);
}

Les formateurs jouent un rôle très important dans l'API Web ASP.NET en matière de prise en charge de négociation de contenu. En effet, l'infrastructure est à présent en mesure de choisir le formateur adéquat en fonction des valeurs reçues dans les en-têtes « Accept » et « Content-Type » du message de demande.

Les méthodes ReadFromStreamAsync et WriteToStreamAsync s'appuient sur la Bibliothèque parallèle de tâches (TPL) pour la réalisation du travail asynchrone afin de renvoyer une instance de tâche. Si vous voulez que votre implémentation formateur fonctionne explicitement de manière synchrone, la classe de base, BufferedMediaTypeFormatter, s'en charge pour vous en interne. Cette classe de base fournit les deux méthodes que vous pouvez remplacer dans une implémentation, SaveToStream et ReadFromStream, qui sont les versions synchrones de SaveToStreamAsync et ReadFromStreamAsync. 

Développement d'un MediaTypeFormatter pour HAL

HAL utilise une sémantique spécifique pour la représentation de ressources et de liens afin que vous ne puissiez pas utiliser tout simplement n'importe quel modèle dans une implémentation d'API Web. C'est pourquoi, afin de simplifier considérablement l'implémentation du formateur, on utilise une classe de base pour la représentation d'une ressource et une autre pour une collection de ressources :

public abstract class LinkedResource
{
  public List<Link> Links { get; set; }
  public string HRef { get; set; }
}
public abstract class LinkedResourceCollection<T> : LinkedResource,
  ICollection<T> where T : LinkedResource
{
  // Rest of the collection implementation
}

Les classes de modèles réelles que les contrôleurs d'API Web utiliseront peuvent être issues de ces deux classes de base. Par exemple, il est possible d'implémenter un produit ou une collection de produits comme suit :

public class Product : LinkedResource
{
  public int Id { get; set; }
  public string Name { get; set; }
  public decimal UnitPrice { get; set; }
}
...
public class Products : LinkedResourceCollection<Product>
{
}

Il est à présent temps, en nous appuyant sur une manière standard de définition de modèles HAL, d'implémenter le formateur. La façon la plus simple de démarrer une nouvelle implémentation de formateur consiste à s'appuyer sur l'une des classes de base suivantes : MediaTypeFormatter ou BufferedMediaTypeFormatter. L'exemple de la figure 6 utilise la deuxième classe de base.

Figure 6 La classe de base BufferedMediaTypeFormatter

public class HalXmlMediaTypeFormatter : BufferedMediaTypeFormatter
{
  public HalXmlMediaTypeFormatter()
    : base()
  {
    this.SupportedMediaTypes.Add(new MediaTypeHeaderValue(
      "application/hal+xml"));
  }
  public override bool CanReadType(Type type)
  {
    return type.BaseType == typeof(LinkedResource) ||
      type.BaseType.GetGenericTypeDefinition() ==
        typeof(LinkedResourceCollection<>);
  }
  public override bool CanWriteType(Type type)
  {
    return type.BaseType == typeof(LinkedResource) ||
     type.BaseType.GetGenericTypeDefinition() ==
       typeof(LinkedResourceCollection<>);
  }
  ...
}

Le code commence par définir dans le constructeur les types de médias pris en charge pour cette implémentation (« application/hal+xml ») puis il remplace les méthodes CanReadType et CanWriteType pour préciser les types .NET pris en charge, qui doivent provenir de Linked­Resource ou LinkedResourceCollection. Du fait qu'elle a été définie dans le constructeur, cette implémentation prend uniquement en charge la variante XML de HAL. Un autre formateur pourrait être implémenté en option pour prendre en charge la variante JSON.

Le véritable travail est effectué dans les méthodes WriteToStream et ReadFromStream, illustrées à la figure 7, qui utiliseront respectivement un XmlWriter et un XmlReader pour écrire et lire un objet dans un flux et à l'extérieur de ce dernier.

Figure 7 Les méthodes WriteToStream et ReadFromStream

public override void WriteToStream(Type type, object value,
  System.IO.Stream writeStream, System.Net.Http.HttpContent content)
{
  var encoding = base.SelectCharacterEncoding(content.Headers);
  var settings = new XmlWriterSettings();
  settings.Encoding = encoding;
  var writer = XmlWriter.Create(writeStream, settings);
  var resource = (LinkedResource)value;
  if (resource is IEnumerable)
  {
    writer.WriteStartElement("resource");
    writer.WriteAttributeString("href", resource.HRef);
    foreach (LinkedResource innerResource in (IEnumerable)resource)
    {
      // Serializes the resource state and links recursively
      SerializeInnerResource(writer, innerResource);
    }
    writer.WriteEndElement();
  }
  else
  {
    // Serializes a single linked resource
    SerializeInnerResource(writer, resource);
  }
  writer.Flush();
  writer.Close();
}
public override object ReadFromStream(Type type,
  System.IO.Stream readStream, System.Net.Http.HttpContent content,
  IFormatterLogger formatterLogger)
{
  if (type != typeof(LinkedResource))
    throw new ArgumentException(
      "Only the LinkedResource type is supported", "type");
  var value = (LinkedResource)Activator.CreateInstance(type);
  var reader = XmlReader.Create(readStream);
  if (value is IEnumerable)
  {
    var collection = (ILinkedResourceCollection)value;
    reader.ReadStartElement("resource");
    value.HRef = reader.GetAttribute("href");
    var innerType = type.BaseType.GetGenericArguments().First();
    while (reader.Read() && reader.LocalName == "resource")
    {
      // Deserializes a linked resource recursively
      var innerResource = DeserializeInnerResource(reader, innerType);
      collection.Add(innerResource);
    }
  }
  else
  {
    // Deserializes a linked resource recursively
    value = DeserializeInnerResource(reader, type);
  }
  reader.Close();
  return value;
}

La dernière étape consiste à configurer l'implémentation du formateur dans le cadre de l'hôte API Web. Cette étape peut être accomplie pratiquement de la même manière que dans l'hébergement automatique d'API Web ASP.NET ou ASP.NET, avec une seule différence dans l'implémentation HttpConfiguration nécessaire. Si l'hébergement automatique utilise une instance HttpSelfHostConfiguration, ASP.NET utilise généralement l'instance HttpConfiguration disponible globalement dans System.Web.Http.GlobalConfiguration.Configuration. La classe HttpConfiguration fournit une collection de formateurs dans laquelle vous pouvez injecter votre propre implémentation de formateur. Voici comment procéder pour ASP.NET :

protected void Application_Start()
{
  Register(GlobalConfiguration.Configuration);
}
public static void Register(HttpConfiguration config)
{
  config.Formatters.Add(new HalXmlMediaTypeFormatter());
}

Une fois le formateur configuré dans le pipeline API Web ASP.NET, tout contrôleur peut simplement renvoyer une classe de modèle issue de LinkedResource pour procéder à sa sérialisation par le formateur en utilisant HAL. Pour le catalogue de produits, par exemple, le produit et la collection des produits représentant le catalogue peuvent être issus de LinkedResource et LinkedResourceCollection, respectivement :

public class Product : LinkedResource
{
  public int Id { get; set; }
  public string Name { get; set; }
  public decimal UnitPrice { get; set; }
}
public class Products : LinkedResourceCollection<Product>
{
}

Le contrôleur ProductCatalogController, qui gère toutes les requêtes pour la ressource du catalogue de produits, peut maintenant retourner des instances Product et Products comme illustré sur la figure 8 pour la méthode Get.

Figure 8 La classe ProductCatalogController

public class ProductCatalogController : ApiController
{
  public static Products Products = new Products
  {
    new Product
    {
      Id = 1,
      Name = "Product 1",
      UnitPrice = 5.34M,
      Links = new List<Link>
      {
        new Link { Rel = "add-cart", HRef = "/api/cart" },
        new Link { Rel = "self", HRef = "/api/products/1" }
      }
    },
    new Product
    {
      Id = 2,
      Name = "Product 2",
      UnitPrice = 10,
      Links = new List<Link>
      {
        new Link { Rel = "add-cart", HRef = "/cart" },
        new Link { Rel = "self", HRef = "/api/products/2" }
      }
    }
  };
  public Products Get()
  {
    return Products;           
  }
}

Cet exemple utilise le format HAL, mais vous pouvez également utiliser une approche similaire pour développer un formateur qui utilise Razor et des modèles pour la sérialisation de modèles dans XHTML. Vous trouverez une implémentation concrète d'un MediaTypeFormatter pour Razor dans RestBugs, un exemple d'application créée par Howard Dierking pour démontrer comment l'API Web ASP.NET peut être utilisée pour créer des API Web hypermédias, à l'adresse suivante : github.com/howarddierking/RestBugs.

Grâce aux formateurs, vous pouvez étendre facilement votre API Web avec de nouveaux types de médias.    

Meilleur support de liaison dans les contrôleurs d'API Web

Il y a définitivement un problème avec l'exemple ProductCatalog­Controller précédent. Tous les liens ont été codés en dur, ce qui peut devenir un sacré casse-tête si les itinéraires changent souvent. La bonne nouvelle réside dans le fait que l'infrastructure fournit une classe d'assistance appelée System.Web.Http.Routing.UrlHelper permettant de déduire automatiquement les liens à partir de la table de routage. Un exemple de cette classe est disponible dans la classe de base ApiController via la propriété Url. Il est donc facile de l'utiliser dans n'importe quelle méthode de contrôleur. Voici à quoi ressemble la définition de la classe UrlHelper :

public class UrlHelper
{
  public string Link(string routeName,
    IDictionary<string, object> routeValues);
  public string Link(string routeName, object routeValues);
  public string Route(string routeName,
    IDictionary<string, object> routeValues);
  public string Route(string routeName, object routeValues);
}

Les méthodes Route renvoient une URL relative pour un itinéraire donné (par exemple, /produits/1) et les méthodes Link renvoient l'URL absolue, qui est celle pouvant être utilisée dans les modèles afin d'éviter tout codage en dur. La méthode Link reçoit deux arguments : le nom de l'itinéraire et les valeurs permettant de composer l'URL.

La figure 9 indique comment, dans l'exemple de catalogue de produits précédent, la classe UrlHelper pourrait être utilisée dans la méthode Get.

Figure 9 Comment la classe UrlHelper pourrait être utilisée dans la méthode Get

public Products Get()
{
  var products = GetProducts();
  foreach (var product in products)
  {
    var selfLink = new Link
    {
      Rel = "self",
      HRef = Url.Route("API Default",
        new
        {
          controller = "ProductCatalog",
          id = product.Id
        })
    };
product.Links.Add(selfLink);
if(product.IsAvailable)
{
    var addCart = new Link
    {
      Rel = "add-cart",
      HRef = Url.Route("API Default",
        new
        {
          controller = "Cart"
        })
    };
    product.Links.Add(addCart);
  }           
}
  return Products;           
}

Le lien « self » pour le produit a été généré à partir de l'itinéraire par défaut en utilisant le nom de contrôleur ProductCatalog et l'ID du produit. Le lien permettant d'ajouter le produit au panier provenait également de l'itinéraire par défaut, mais utilisait à la place le nom de contrôleur Cart. Comme vous pouvez le voir sur la figure 9, le lien permettant d'ajouter le produit au panier est associé à la réponse basée sur la disponibilité du produit (product.IsAvailable). La logique pour fournir des liens au client dépendra globalement des règles métier généralement appliquées dans les contrôleurs.    

Pour résumer

Hypermédia est une fonction puissante qui permet aux clients et serveurs d'évoluer indépendamment. En utilisant des liens ou d'autres artefacts hypermédias tels que des formulaires proposés par le serveur à différentes étapes, les clients peuvent être découplés avec succès du flux de travail métier du serveur qui dirige l'interaction.

Pablo Cibraro est un expert de renommée internationale fort de plus de 12 années d'expérience dans la conception et l'implémentation de grands systèmes distribués avec les technologies Microsoft. Il est un MVP pour les systèmes connectés. Au cours des neuf dernières années, Pablo Cibraro a aidé de nombreuses équipes Microsoft à développer des outils et des infrastructures permettant de développer des applications orientées service avec Services Web, Windows Communication Foundation, ASP.NET et Windows Azure. Il possède un blog sur weblogs.asp.net/cibrax et vous pouvez le suivre sur Twitter, à l'adresse suivante : twitter.com/cibrax.

Merci à l'expert technique suivant d'avoir relu cet article : Daniel Roth