Partager via


Guide du développeur pour le kit de démarrage REST WCF

Aaron Skonnard, Pluralsight

Août 2009

REMARQUE : Ce document est basé sur la version 2 du kit de démarrage REST WCF Preview 2.

Aperçu

Windows Communication Foundation (WCF) 3.5 a introduit un modèle de programmation « Web » pour la création de services RESTful dans .NET. Bien que WCF 3.5 constitue une base solide pour la création d’un large éventail de services RESTful, les développeurs doivent toujours implémenter un grand nombre de code de plaque de chaudière pour chaque service RESTful qu’ils créent et pour traiter directement avec les fonctionnalités importantes du protocole HTTP. Le Kit de démarrage REST WCF fournit un ensemble d’extensions WCF et de modèles de projet qui visent à simplifier encore davantage le développement REST. Bien que le kit de démarrage REST WCF soit actuellement considéré comme une technologie « préversion », bon nombre de ses fonctionnalités trouveront probablement leur chemin dans les futures versions du .NET Framework.

Dans ce livre blanc, nous allons explorer entièrement les différentes fonctionnalités du Kit de démarrage REST WCF et vous montrer comment commencer à les mettre à bon usage pour gérer certains des scénarios de développement REST les plus courants aujourd’hui. Microsoft s’engage à offrir une plateforme riche pour les services RESTful dans le cadre de Microsoft .NET.

Si vous ne connaissez pas les concepts REST ou le modèle de programmation REST WCF 3.5, veillez à lire un guide de conception et de création de services RESTful avec WCF 3.5 avant de continuer.

Présentation du kit de démarrage REST WCF

Depuis la publication de WCF 3.5, Microsoft a travaillé dur pour rendre le processus de création et de consommation de services RESTful encore plus facile sur la plateforme .NET. L’un des résultats de cet effort est une nouvelle suite de classes d’assistance, de méthodes d’extension et de modèles de projet Visual Studio empaquetés dans ce qu’on appelle le kit de démarrage REST WCF. Aujourd’hui, le Kit de démarrage REST WCF peut être téléchargé à partir de CodePlex, mais la plupart de ses fonctionnalités peuvent trouver leur chemin dans les futures versions du .NET Framework officiel. Vous trouverez les dernières informations sur le Kit de démarrage REST WCF sur la page d’accueil MSDN WCF REST.

Le kit de démarrage REST WCF (préversion 2) est fourni avec trois nouveaux assemblys .NET que vous pouvez tirer parti de votre code pour simplifier les tâches de programmation REST courantes (voir figure 1).

Nom de l’assembly Description

Microsoft.ServiceModel.Web

Contient un ensemble de nouvelles classes et méthodes d’extension qui simplifient le processus de génération et d’hébergement de services RESTful à l’aide de WCF.

Microsoft.Http

Contient un ensemble de nouvelles classes et méthodes d’extension qui simplifient le processus de consommation de services RESTful à l’aide de HTTP.

Microsoft.Http.Extensions

Contient un ensemble de nouvelles classes et méthodes d’extension pour consommer des types spécifiques de services RESTful et des formats de réponse.

Figure 1 : Assemblys du kit de démarrage REST WCF

L’assembly Microsoft.ServiceModel.Web contient une nouvelle classe WebServiceHost2 (dérivée de WebServiceHost dans WCF 3.5) conçue spécifiquement pour l’hébergement de services RESTful. Cette classe démarre plusieurs fonctionnalités propres à REST et configure le runtime WCF sous-jacent d’une manière qui rendra vos services RESTful plus faciles à générer et plus faciles à consommer. Ce nouvel assembly est également fourni avec des attributs .NET et des méthodes d’extension que vous pouvez également tirer parti de votre code. Ces extensions vous permettent d’exploiter les différentes fonctionnalités offertes par WebServiceHost2.

L’assembly Microsoft.Http contient une nouvelle API HTTP côté client pour consommer des services RESTful. La classe principale d’intérêt dans cet assembly est HttpClient. Avec HttpClient, vous pouvez facilement émettre des requêtes HTTP GET, POST, PUT et DELETE et traiter la réponse via diverses API spécifiques au contenu. Il facilite également l’envoi de données de formulaire et de valeurs de chaîne de requête, et simplifie le processus d’utilisation des en-têtes HTTP via un nouvel ensemble de classes d’en-tête typées. Cette nouvelle API côté client offre une expérience HTTP plus naturelle pour consommer n’importe quel service RESTful trouvé sur le web.

Enfin, l’assembly Microsoft.Http.Extensions contient quelques classes dérivées de HttpClient spécialisées qui se concentrent sur des scénarios spécifiques. Il fournit également quelques méthodes d’extension axées sur le traitement du corps d’un message HTTP dans différents formats (XML, JSON, flux Atom, etc.). Vous tirerez parti de ces méthodes d’extension conjointement avec HttpClient lors de l’utilisation d’un service.

Le Kit de démarrage REST WCF est également fourni avec un ensemble de modèles de projet Visual Studio utiles (voir la figure 2) qui ciblent des scénarios REST courants. Ces modèles de projet fournissent le code réutilisable que vous devez commencer, en tirant parti des nouvelles classes/extensions trouvées dans les assemblys décrits ci-dessus. Par exemple, il existe un modèle pour produire un service « singleton » (expose une ressource unique) et un autre pour produire un service « collection » (expose une collection de ressources). Il existe un autre modèle pour produire un flux Atom et un autre qui produit un service AtomPub entièrement fonctionnel. Ces modèles peuvent aider à démarrer votre implémentation de service pour ces différents scénarios REST.

Modèle de projet Description

REST Singleton Service

Produit un service qui définit un exemple de ressource singleton (SampleItem) et l’interface HTTP complète pour interagir avec le singleton (GET, POST, PUT et DELETE) avec prise en charge des représentations XML et JSON.

Service de collecte REST

Similaire au service REST Singleton uniquement, il prend également en charge la gestion d’une collection de ressources SampleItem.

Service de flux Atom

Produit un service qui expose un exemple de flux Atom avec des données factices.

AtomPub Service

Produit un service AtomPub entièrement fonctionnel capable de gérer des collections de ressources ainsi que des entrées multimédias.

Service XML brut HTTP

Produit un service avec des méthodes GET et POST simples que vous pouvez créer pour les services XML simples (POX) qui ne sont pas entièrement conformes aux principes de conception RESTful, mais s’appuient uniquement sur les opérations GET et POST.

Figure 2 : Modèles de projet du kit de démarrage REST WCF

Dans les sections qui suivent, nous allons examiner plus en détail ces assemblys et modèles de projet, ainsi que la façon dont nous allons examiner comment gérer certains scénarios REST courants.

Création et hébergement de services avec Microsoft.ServiceModel.Web

Pour commencer à tirer parti du Kit de démarrage REST WCF dans vos projets de service WCF, vous devez modifier votre application hôte pour utiliser la classe WebServiceHost2 trouvée dans Microsoft.ServiceModel.Web. Cette classe démarre les nouvelles fonctionnalités du kit de démarrage REST WCF pour tous les points de terminaison de service exposés par l’application hôte. Une fois cette opération effectuée, vous pouvez commencer à tirer parti de la page d’aide automatique, de la prise en charge de la mise en cache HTTP, du nouveau comportement de gestion des exceptions et d’une nouvelle fonctionnalité d’interception des requêtes. Tout d’abord, je vais vous montrer comment connecter WebServiceHost2, puis nous explorerons chacun de ces nouveaux domaines de fonctionnalités.

Hébergement de services REST avec WebServiceHost2

La classe WebServiceHost2 dérive de la classe WebServiceHost trouvée dans WCF 3.5. Par conséquent, vous pouvez l’utiliser comme n’importe quelle autre classe dérivée de ServiceHost. Si vous utilisez des techniques d’hébergement automatique dans l’application hôte, vous disposez probablement d’un code semblable à celui-ci pour l’instant :

WebServiceHost host = new WebServiceHost(typeof(BookmarkService));
host.Open();

Pour commencer à utiliser les fonctionnalités du Kit de démarrage REST WCF avec votre service, il vous suffit de remplacer le nom de classe « WebServiceHost » par « WebServiceHost2 » dans votre code :

WebServiceHost2 host = new WebServiceHost2(typeof(BookmarkService));
host.Open();

Vous pouvez également tirer parti de WebServiceHost2 lors de l’hébergement de services dans IIS.  Si vous hébergez vos services WCF à l’intérieur d’IIS aujourd’hui, vous disposez d’un fichier SVC qui ressemble à ceci :

<%@ ServiceHost Language="C#" Debug="true" Service="BookmarkService"
    Factory="System.ServiceModel.Activation.WebServiceHostFactory"%>

Le Kit de démarrage REST WCF est fourni avec une nouvelle classe WebServiceHost2Factory : il est responsable de l’activation des instances WebServiceHost2. Remplacez simplement la classe de fabrique par WebServiceHost2Factory et vos services hébergés par IIS seront gérés automatiquement par les instances WebServiceHost2 :

<%@ ServiceHost Language="C#" Debug="true" Service="BookmarkService"
    Factory="Microsoft.ServiceModel.Web.WebServiceHost2Factory"%>

Par conséquent, que fait exactement WebServiceHost2 différemment de la classe WebServiceHost fournie avec WCF 3.5 ?  Il fait deux choses clés.  Tout d’abord, il remplace webHttpBehavior sur tous les points de terminaison par une instance de WebHttpBehavior2. La nouvelle classe WebHttpBehavior2 est chargée de fournir la fonctionnalité de page d’aide automatique et la logique de gestion des erreurs « Web » du serveur. Ensuite, il ajoute un nouvel élément de liaison à chaque point de terminaison pour injecter la nouvelle logique d’interception de requête. Par conséquent, en modifiant simplement le type d’hôte, vos services REST WCF pourront tirer parti de ces nouvelles fonctionnalités.

Page d’aide automatique

Une fois que vous utilisez WebServiceHost2, vos services bénéficieront automatiquement des avantages de la nouvelle fonctionnalité de page d’aide automatique, qui constitue une étape importante pour les services RESTful. Vous pouvez voir la page d’aide en accédant à l’adresse de base de votre service avec « aide » ajoutée à la fin (voir la figure 3).

La page d’aide fournit une description lisible par l’homme de chaque opération de service WCF annotée avec [WebGet] ou [WebInvoke], et pour chacune d’elles, elle décrit le modèle d’URI, l’opération HTTP prise en charge et les formats de requête/réponse, essentiellement tout ce qu’un consommateur doit savoir.

Pour chaque demande/réponse, la page d’aide fournit également un schéma XML et un exemple d’instance XML correspondant que les consommateurs peuvent utiliser pour s’intégrer au service. Les consommateurs peuvent utiliser le schéma pour générer des types sérialisables côté client appropriés ou ils peuvent simplement inspecter l’exemple de document XML pour déterminer manuellement comment écrire le code de traitement XML approprié. Les deux approches sont utiles.

Figure 3 : Page d’aide automatique pour les services RESTFul

Il est important de noter que la page d’aide est retournée en tant que flux Atom. La plupart des navigateurs web fournissent un rendu de flux intégré pour faciliter l’affichage humain, c’est-à-dire ce que fait Internet Explorer dans la figure. Toutefois, étant donné qu’il s’agit d’un flux, les consommateurs peuvent également consommer par programmation la description si vous le souhaitez. Si vous deviez désactiver la « vue de lecture de flux » dans les options d’Internet Explorer, vous verrez réellement le code XML du flux affiché à la place . Vous pouvez également inspecter le code XML du flux en affichant la source de la page.

Par défaut, la page d’aide est mise à disposition à l’adresse de base avec « aide » ajoutée à la fin, mais vous pouvez personnaliser l’adresse de la page d’aide via la propriété HelpPageLink sur WebServiceHost2.

Vous pouvez également ajouter une description lisible humaine à chaque opération RESTful via le nouvel attribut [WebHelp] trouvé dans Microsoft.ServiceModel.Web, comme illustré dans l’exemple suivant :

[WebHelp(Comment = "Returns the user account details for the authenticated user.")]
[WebGet(UriTemplate = BookmarkServiceUris.User)]
[OperationContract]
User GetUserAsXml(string username)
{
    return HandleGetUser(username);
}

Maintenant, lorsque vous réexécutez l’application hôte et revenez à la page d’aide, vous verrez ce texte de commentaire apparaître dans la description GetUserAsXml (voir la figure 4).

Figure 4 : Page d’aide automatique avec description personnalisée

Cette nouvelle page d’aide rend automatiquement vos services RESTful plus détectables, ce qui facilite leur consommation. Vos consommateurs peuvent découvrir la conception de l’URI du service, les opérations HTTP prises en charge et les formats de requête/réponse, et votre description reste toujours synchronisée avec votre code WCF, comme le fonctionnement des opérations avec les services web ASP.NET.

Vous n’obtenez toujours pas une expérience complète de génération de code côté client (la WSDL), mais lorsque vous combinez cette page d’aide avec la nouvelle fonctionnalité HttpClient, vous n’en avez vraiment pas besoin.

ExceptionHandling

L’un des aspects les plus fastidieux de l’implémentation des services RESTful traite directement de certains des détails du protocole HTTP, tels que le renvoi des codes d’état et des descriptions HTTP appropriés, en particulier dans le contexte d’une opération de service WCF. Le code suivant montre comment rechercher des utilisateurs non autorisés et retourner une réponse 401 « Non autorisé » si nécessaire :

if (!IsUserAuthorized(username))  {
    WebOperationContext.Current.OutgoingResponse.StatusCode =
        HttpStatusCode.Unauthorized;
    WebOperationContext.Current.OutgoingResponse.StatusDescription = "Unauthorized";
    return null;
}

Ce code n’est pas terriblement difficile, mais il ne retourne pas également un message de réponse détaillé, qui est généralement très utile pour les consommateurs. Lorsque vous souhaitez renvoyer ce type de message de réponse détaillé, la complexité augmente considérablement, car il n’existe pas un moyen facile de le faire dans l’opération WCF.

Pour simplifier ce scénario courant, le Kit de démarrage REST WCF fournit une nouvelle classe WebProtocolException qui facilite vraiment le retour d’une erreur HTTP à l’appelant. Vous lèvez simplement une instance de WebProtocolException dans vos opérations de service WCF, en spécifiant le code d’état HTTP et le message d’erreur, et (en supposant que vous utilisez WebServiceHost2) le runtime sous-jacent s’occupe de produire le message de réponse HTTP approprié.

L’exemple suivant montre comment lever quelques instances WebProtocolException différentes spécifiant différents codes d’état HTTP et messages d’erreur :

if (!IsUserAuthorized(username)) {
    throw new WebProtocolException(HttpStatusCode.Unauthorized,
        "Missing or invalid user key (supply via the Authorization header)", null);
}
if (bookmark_id <= 0)
    throw new WebProtocolException(HttpStatusCode.BadRequest,
        "The bookmark_id field must be greater than zero", null);

Une fois qu’une exception WebProtocolException est levée, elle sera gérée par un gestionnaire d’erreurs WCF personnalisé (introduit par WebHttpBehavior2). Le gestionnaire d’erreurs traduit l’instance WebProtocolException en message de réponse HTTP approprié contenant une réponse détaillée pour l’avantage du consommateur.

La figure 5 illustre l’apparence de la première exception WebProtocolException lors du rendu dans un navigateur. Notez que le XHTML résultant affiche clairement le code d’état HTTP avec un message « détail ». Ce modèle XHTML standard est intégré à la classe WebProtocolException. Par conséquent, si vous aimez le fonctionnement, vous n’avez rien à faire et vos consommateurs recevront quelque chose de raisonnable.

Figure 5 : WebProtocolException rendue dans un de navigateur

Si, toutefois, vous souhaitez personnaliser le XHTML résultant, vous pouvez utiliser l’une des autres surcharges de constructeur et fournir le XHTML précis que vous souhaitez retourner comme illustré ici :

if (!IsUserAuthorized(username)) {
    throw new WebProtocolException(HttpStatusCode.Unauthorized, "Unauthorized",
        new XElement("html",
            new XElement("body"),
                new XElement("h1", "Unauthorized"),
                new XElement("p", "Missing or invalid user key " +
                "(supply via the Authorization header)")), true, null);
}

La figure 6 montre les résultats de la levée de cette exception WebProtocolException lors du rendu dans un navigateur :

Figure 6 : WebProtocolException avec une réponse XHTML personnalisée

Cette approche vous donne une liberté complète sur la réponse XHTML.

Toutefois, si vous n’êtes pas préoccupé par le renvoi d’un message d’erreur lisible par l’homme (en XHTML), vous pouvez retourner un type personnalisé qui sera sérialisé en XML (à l’aide de DataContractSerializer). L’exemple de code suivant montre comment effectuer cette opération à l’aide d’un type personnalisé appelé CustomErrorMessage :

if (!IsUserAuthorized(username)) {
    throw new WebProtocolException(HttpStatusCode.Unauthorized,
        "Custom error message",
        new CustomErrorMessage() {
            ApplicationErrorCode = 5000,
            Description = "Authentication token missing" },
        null);
}

Dans ce cas, le consommateur reçoit le message XML personnalisé illustré dans la figure 7.

Figure 7 : WebProtocolException avec une réponse XML personnalisée

En outre, si l’opération WCF spécifie WebMessageFormat.Json pour la réponse, le code XML résultant sera sérialisé à l’aide de DataContractJsonSerializer afin de retourner JSON au consommateur.

Vous pouvez même aller plus loin en intégrant la fonctionnalité d’erreur personnalisée ASP.NET. Pour ce faire, vous pouvez ajouter du code à Global.asax pour vérifier l’existence d’une WebProtocolException et rediriger le consommateur vers une page d’erreur personnalisée lorsqu’il en trouve un :

protected void Application_EndRequest(object sender, EventArgs e)
{
    if (HttpContext.Current.Error != null)
    {
        WebProtocolException webEx =
            HttpContext.Current.Error as WebProtocolException;
        if (webEx != null && webEx.StatusCode == HttpStatusCode.BadRequest)
        {
            HttpContext.Current.ClearError();
            HttpContext.Current.Response.Redirect("BadRequest.htm");
        }
        if (webEx != null && webEx.StatusCode == HttpStatusCode.Unauthorized)
        {
            HttpContext.Current.ClearError();
            HttpContext.Current.Response.Redirect("Unauthorized.htm");
        }
    }
}

Il existe deux exemples dans le KIT de démarrage REST WCF qui illustrent le fonctionnement de ces fonctionnalités WebProtocolException , une appelée « WebException » et une autre appelée « WebException2 ». En fin de compte, ces fonctionnalités facilitent considérablement la production de messages d’erreur HTTP personnalisés contenant des messages de réponse descriptifs en lisant simplement des exceptions, qui est un modèle plus naturel pour les développeurs .NET.

Prise en charge de la mise en cache

L’un des principaux avantages potentiels de REST est la mise en cache HTTP. Toutefois, pour réaliser cet avantage, vous devez tirer parti des différents en-têtes de mise en cache HTTP dans vos messages de demande et de réponse. Pour ce faire, vous pouvez effectuer cette opération dans vos opérations de service WCF en accédant manuellement aux en-têtes de requête/réponse via l’instance WebOperationContext, mais il n’est pas trivial de procéder correctement.

Le Kit de démarrage REST WCF fournit également un modèle plus simple pour contrôler la mise en cache via l’attribut [WebCache] que vous pouvez appliquer de manière déclarative à vos différentes opérations GET. Cet attribut vous permet de spécifier un profil de mise en cache pour chaque opération, puis le comportement de mise en cache (CachingParameterInspector) prend soin de gérer tous les détails de mise en cache HTTP sous-jacents.

L’implémentation [WebCache] s’appuie sur ASP.NET mise en cache de sortie et fournit la plupart des mêmes propriétés trouvées sur la classe System.Web.UI.OutputCacheParameters. Il intègre simplement ce même comportement au runtime WCF et facilite l’application déclarative de ce comportement à vos opérations de service WCF. L’exemple suivant montre comment mettre en cache une réponse [WebGet] pendant 60 secondes :

[WebCache(Duration = 60)]
WebGet(UriTemplate = BookmarkServiceUris.PublicBookmarks)]
OperationContract]
ookmarks GetPublicBookmarksByTagAsXml(string tag)
   return HandleGetPublicBookmarks(tag);

Toutefois, étant donné que cette opération peut retourner une réponse différente pour chaque « balise » fournie, nous voulons vraiment varier la réponse mise en cache en fonction d’une balise par balise. Pour ce faire, utilisez la propriété VaryByParam (que vous connaissez peut-être de ASP.NET), comme illustré ici :

[WebCache(Duration = 60, VaryByParam = "tag")]
[WebGet(UriTemplate = BookmarkServiceUris.PublicBookmarks)]
[OperationContract]
Bookmarks GetPublicBookmarksByTagAsXml(string tag)
{
    return HandleGetPublicBookmarks(tag);
}

En plus de VaryByParam, l’attribut [WebCache] fournit VaryByHeader et VaryByCustom, ce qui vous permet de spécifier différentes variables pour influencer l’entrée du cache de sortie. Vous pouvez également utiliser l’emplacement pour contrôler où la réponse est autorisée à être mise en cache (par exemple, Any, Client, Server, ServerAndClient, etc.) et pour empêcher la mise en cache, vous pouvez définir la propriété NoStore sur « true ».

[WebCache] prend également en charge la propriété CacheProfile, qui vous permet de référencer un « profil de cache de sortie » trouvé dans web.config. Par exemple, le web.config suivant contient un profil de cache de sortie appelé « CacheFor1Min » qui spécifie que la réponse sera mise en cache pendant 60 secondes, la réponse mise en cache peut être stockée n’importe où et l’entrée du cache varie en fonction du paramètre « tag » :

<configuration>
  <system.web>
    <compilation debug="true"/>
    <caching>
      <outputCacheSettings>
        <outputCacheProfiles>
          <clear/>
          <add name="CacheFor1Min" duration="60" enabled="true"
               location="Any" varyByParam="tag"/>
        </outputCacheProfiles>
      </outputCacheSettings>
    </caching>
  </system.web>
  <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
  </system.serviceModel>
</configuration>

Cela vous permet de dissocier le comportement de mise en cache de votre code WCF compilé. Vous pouvez appliquer ce profil de cache de sortie à vos opérations WCF via l’attribut [WebCache] comme indiqué ici :

[WebCache(CacheProfileName="CacheFor1Min")]
WebGet(UriTemplate = BookmarkServiceUris.PublicBookmarks)]
OperationContract]
ookmarks GetPublicBookmarksByTagAsXml(string tag)
   return HandleGetPublicBookmarks(tag);

Enfin, l’attribut [WebCache] vous permet même de lier le comportement de mise en cache à une dépendance SQL via la propriété SqlDependency. Pour tirer parti de cela, vous devez ajouter une entrée <sqlCacheDependency> pour la base de données en question à web.config. Ensuite, vous utilisez la propriété SqlDependency de l’attribut [WebCache] pour spécifier une liste de paires de noms de table & de base de données dont l’entrée de cache doit dépendre. L’entrée du cache expire lorsque l’une des tables spécifiées est modifiée.

La web.config suivante illustre comment configurer une nouvelle entrée <sqlCacheDependency> :

<configuration>
  <connectionStrings>
    <add name="bmconn" connectionString=
     "Data Source=.; Initial Catalog=BookmarksDB; Integrated Security=true" />
  </connectionStrings>
    <system.web>
    <caching>
      <sqlCacheDependency enabled="true" pollTime="1000" >
        <databases>
          <add name="bmdb" connectionStringName="bmconn" />
        </databases>
      </sqlCacheDependency>
    </caching>
  </system.web>
  <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
  </system.serviceModel>
</configuration>

Vous pouvez ensuite appliquer [WebCache] à une opération pour lier le comportement de mise en cache de sortie à une table spécifique dans la base de données SQL. L’exemple suivant lie la mise en cache de sortie à la table « Signets » :

[WebCache(SqlDependency="bmdb:Bookmarks", VaryByParam = "tag")]
WebGet(UriTemplate = BookmarkServiceUris.PublicBookmarks)]
OperationContract]
ookmarks GetPublicBookmarksByTagAsXml(string tag)
   return HandleGetPublicBookmarks(tag);

Avec cela en place, la sortie de cette opération particulière est mise en cache (pour chaque « balise ») unique jusqu’à ce que les données de la table Signets sous-jacentes changent.

L’attribut [WebCache] vous permet de tirer parti de la mise en cache HTTP sans avoir à utiliser directement les en-têtes de mise en cache HTTP. Le comportement WCF sous-jacent s’occupe de l’injection des en-têtes HTTP Cache-Control, Date, Expires et Vary HTTP dans la réponse, que les clients peuvent ensuite tirer parti de mettre en cache la réponse et de réduire le nombre d’allers-retours futurs.

Le KIT de démarrage REST WCF est fourni avec deux exemples complets qui illustrent l’utilisation de la fonctionnalité [WebCache] plus en détail , un appelé « Mise en cache1 » et un autre appelé « Mise en cache2 », l’exemple « Mise en cache2 » fournit un exemple complet à l’aide d’une dépendance de cache SQL.

Outre la fonctionnalité [WebCache], le kit de démarrage REST WCF est également fourni avec quelques méthodes d’extension qui facilitent l’utilisation d’ETags, ce qui simplifie le processus d’implémentation de scénarios GET et PUT conditionnels conditionnels. Je vais vous montrer un exemple de ceci plus tard dans le document.

Interception des demandes

Un autre besoin courant lors de la création de services RESTful est « l’interception des demandes ». Par exemple, lorsque vous devez implémenter un comportement de service qui va s’appliquer à toutes les opérations (par exemple, l’authentification ou la logique de distribution personnalisée), il est généralement préférable de l’implémenter en tant que comportement WCF, car le modèle de comportement vous permet d’injecter un « intercepteur » de traitement des demandes dans le runtime. Le seul problème est d’écrire des comportements wcf et des intercepteurs est assez complexe et pas pour l’évanouissement du cœur. Par conséquent, pour simplifier ce scénario courant, le kit de démarrage REST WCF fournit un mécanisme d'« interception de requête » beaucoup plus simple qui vous protège de l’écriture des composants d’extensibilité WCF plus complexes.

Dans Microsoft.ServiceModel.Web, vous trouverez une nouvelle classe de base abstraite appelée RequestInterceptor qui définit une méthode abstraite unique appelée ProcessRequest. Voici la définition de classe complète :

public abstract class RequestInterceptor
{
    protected RequestInterceptor(bool isSynchronous);
    public bool IsSynchronous { get; }
    public virtual IAsyncResult BeginProcessRequest(RequestContext context,
        AsyncCallback callback, object state);
    public virtual RequestContext EndProcessRequest(IAsyncResult result);
    public abstract void ProcessRequest(ref RequestContext requestContext);
}

Vous dérivez une classe de RequestInterceptor et remplacez ProcessRequest (et BeginProcessRequest/EndProcessRequest si vous souhaitez prendre en charge les appels asynchrones). Votre implémentation de ProcessRequest est l’endroit où vous implémentez la logique d’interception des requêtes. Notez que vous avez fourni une instance de contexte de requête, qui vous donne accès au message de requête et fournit quelques méthodes pour court-circuiter le pipeline de requête et retourner un message de réponse.

La classe WebServiceHost2 gère la collection d’instances RequestInterceptor configurées pour un service particulier. Vous ajoutez simplement des instances RequestInterceptor à la collection Interceptors avant d’appeler Open sur l’instance hôte. Ensuite, lorsque vous appelez Open, ils sont insérés dans le pipeline de traitement des demandes en arrière-plan (via un nouvel élément de liaison).

L’exemple suivant montre comment implémenter un RequestInterceptor qui effectue l’authentification par clé API et rejette les demandes non autorisées :

public class AuthenticationInterceptor : RequestInterceptor
{
    public AuthenticationInterceptor() : base(false) { }
    public override void ProcessRequest(ref RequestContext requestContext)
    {
        if (!IsValidApiKey(requestContext))
            GenerateErrorResponse(requestContext,
                HttpStatusCode.Unauthorized,
                "Missing or invalid user key (supply via the Authorization header)");
    }
    public bool IsValidUserKey(Message req, string key, string uri)
    {
        ... // ommitted for brevity
    }
    public void GenerateErrorResponse(RequestContext requestContext,
        HttpStatusCode statusCode, string errorMessage)
    {
        // The error message is padded so that IE shows the response by default
        string errorHtml =
            "<html><HEAD><TITLE>Request Error</TITLE></HEAD><BODY>" +
            "<H1>Error processing request</H1><P>{0}</P></BODY></html>";
        XElement response = XElement.Load(new StringReader(
            string.Format(errorHtml, errorMessage)));
        Message reply = Message.CreateMessage(MessageVersion.None, null, response);
        HttpResponseMessageProperty responseProp = new HttpResponseMessageProperty()
        {
            StatusCode = statusCode
        };
        responseProp.Headers[HttpResponseHeader.ContentType] = "text/html";
        reply.Properties[HttpResponseMessageProperty.Name] = responseProp;
        requestContext.Reply(reply);
        // set the request context to null to terminate processing of this request
        requestContext = null;
    }
}

À présent, vous pouvez tirer parti de cet intercepteur de requête avec vos services hébergés par IIS en écrivant un ServiceHostFactory personnalisé qui injecte l’intercepteur lorsque l’instance WebServiceHost2 est créée pour la première fois. L’exemple suivant montre comment effectuer cette opération :

public class SecureWebServiceHostFactory : ServiceHostFactory
{
    protected override ServiceHost CreateServiceHost(Type serviceType,
        Uri[] baseAddresses)
    {
        WebServiceHost2 host = new WebServiceHost2(serviceType, true, baseAddresses);
        host.Interceptors.Add(new AuthenticationInterceptor());
        return host;
    }
}

Ensuite, vous spécifiez simplement SecureWebServiceHostFactory dans vos fichiers .svc (à l’aide de l’attribut Factory) et votre intercepteur se lance automatiquement. Cela s’avère beaucoup plus facile que d’écrire le comportement WCF, l’intercepteur et les classes d’attribut équivalentes qui seraient nécessaires pour extraire cette opération.

Le KIT de démarrage REST WCF est fourni avec quelques exemples supplémentaires qui illustrent certaines des possibilités avec RequestInterceptor. L’un des exemples montre comment implémenter le comportement X-HTTP-Method-Override avec un RequestInterceptor. Voici l’implémentation requestInterceptor :

public class XHttpMethodOverrideInterceptor : RequestInterceptor
{
    public XHttpMethodOverrideInterceptor() : base(true) {}
    public override void ProcessRequest(ref RequestContext requestContext)
    {
        if (requestContext == null || requestContext.RequestMessage == null)
        {
            return;
        }
        Message message = requestContext.RequestMessage;
        HttpRequestMessageProperty reqProp = (HttpRequestMessageProperty)
            message.Properties[HttpRequestMessageProperty.Name];
        string methodOverrideVal = reqProp.Headers["X-HTTP-Method-Override"];
        if (!string.IsNullOrEmpty(methodOverrideVal))
        {
            reqProp.Method = methodOverrideVal;
        }
    }
}

Il recherche l’en-tête X-HTTP-Method-Override et, s’il en trouve un, il réinitialise la méthode HTTP du message de requête à la valeur trouvée dans l’en-tête. Cela fournit une solution très simple à ce scénario et peut facilement être réutilisée sur toutes vos solutions de service WCF RESTful.

Un autre exemple RequestInterceptor qu’ils fournissent avec le Kit de démarrage REST WCF est la répartition basée sur le type de contenu. En d’autres termes, il est possible de distribuer à différentes opérations de service en fonction de la valeur des en-têtes de requête HTTP Accept ou Content-Type. L’exemple suivant montre comment procéder par le biais d’une autre implémentation RequestInterceptor :

public class ContentTypeRequestInterceptor : RequestInterceptor
{
    public ContentTypeRequestInterceptor() : base(true) {}
    public override void ProcessRequest(ref RequestContext requestContext)
    {
        if (requestContext == null) return;
        Message request = requestContext.RequestMessage;
        if (request == null) return;
        HttpRequestMessageProperty prop = (HttpRequestMessageProperty)
            request.Properties[HttpRequestMessageProperty.Name];
        string format = null;
        string accepts = prop.Headers[HttpRequestHeader.Accept];
        if (accepts != null)
        {
            if (accepts.Contains("text/xml") || accepts.Contains("application/xml"))
            {
                format = "xml";
            }
            else if (accepts.Contains("application/json"))
            {
                format = "json";
            }
        }
        else
        {
            string contentType = prop.Headers[HttpRequestHeader.ContentType];
            if (contentType != null)
            {
                if (contentType.Contains("text/xml") ||
                    contentType.Contains("application/xml"))
                {
                    format = "xml";
                }
                else if (contentType.Contains("application/json"))
                {
                    format = "json";
                }
            }
        }
        if (format != null)
        {
            UriBuilder toBuilder = new UriBuilder(request.Headers.To);
            if (string.IsNullOrEmpty(toBuilder.Query))
            {
                toBuilder.Query = "format=" + format;
            }
            else if (!toBuilder.Query.Contains("format="))
            {
                toBuilder.Query += "&format=" + format;
            }
            request.Headers.To = toBuilder.Uri;
        }
    }
}

Voici quelques exemples de ce que vous pouvez accomplir avec le mécanisme RequestInterceptor. Vous pouvez utiliser cette technique pour accomplir différents comportements de traitement des demandes, tels que la journalisation, la validation ou même la mise en cache personnalisée. Et la solution est facile à réutiliser sur vos services RESTful.

Classes et méthodes d’extension supplémentaires

Outre les principales fonctionnalités que j’ai décrites, le kit de démarrage REST WCF est également fourni avec différentes méthodes d’extension qui simplifient les tâches de programmation REST courantes. Ces méthodes d’extension sont réparties entre plusieurs classes dans Microsoft.ServiceModel.Web. Je vais mettre en évidence quelques-uns d’entre eux ici.

La classe WebOperationContextExtensions contient un ensemble de méthodes d’extension pour la classe WebOperationContext principale. Certains d’entre eux sont conçus pour faciliter la manipulation d’URI et d’UriTemplate (GetBaseUri, GetRequestUri et BindTemplateToRequestUri). Et le reste est conçu pour simplifier le traitement eTag HTTP (via plusieurs surcharges SetHashEtag et ThrowIfEtagMissingOrStale). Voici la définition de classe pour WebOperationContextExtensions :

public static class WebOperationContextExtensions
{
    public static Uri BindTemplateToRequestUri(this WebOperationContext context,
        UriTemplate template, params string[] values);
    public static Uri GetBaseUri(this IncomingWebRequestContext context);
    public static NameValueCollection GetQueryParameters(
        this IncomingWebRequestContext context);
    public static Uri GetRequestUri(this IncomingWebRequestContext context);
    public static string SetHashEtag<T>(this OutgoingWebResponseContext context,
        T entityToHash);
    public static string SetHashEtag<T>(this OutgoingWebResponseContext context,
        BinaryFormatter formatter, T entityToHash);
    public static string SetHashEtag<T>(this OutgoingWebResponseContext context,
        XmlObjectSerializer serializer, T entityToHash);
    public static string SetHashEtag<T>(this OutgoingWebResponseContext context,
        XmlSerializer serializer, T entityToHash);
    public static void ThrowIfEtagMissingOrStale(
        this IncomingWebRequestContext context, string expectedEtag);
}

La classe SerializationExtensions contient plusieurs méthodes d’extension qui simplifient la sérialisation des objets vers et depuis des instances XElement (lors de l’utilisation de XLinq). Il fournit plusieurs surcharges ToObject et ToXml :

public static class SerializationExtensions
{
    public static TObject ToObject<TObject>(this XElement xml);
    public static TObject ToObject<TObject>(this XElement xml,
        XmlObjectSerializer serializer);
    public static TObject ToObject<TObject>(this XElement xml,
        XmlSerializer serializer);
    public static XElement ToXml<TObject>(TObject obj);
    public static XElement ToXml<TObject>(TObject obj,
        XmlObjectSerializer serializer);
    public static XElement ToXml<TObject>(TObject obj, XmlSerializer serializer);
}

Enfin, la classe SyndicationExtensions contient plusieurs méthodes d’extension qui simplifient l’utilisation des flux RSS/Atom via les classes SyndicationFeed et SyndicationItem. Ces méthodes facilitent l’ajout de différents types de liens à un flux, notamment les liens « self », les liens « modifier » et les liens de navigation :

public static class SyndicationExtensions
{
    public static void AddEditLink(this SyndicationItem entry, Uri uri);
    public static void AddEditMediaLink(this SyndicationItem entry, Uri uri,
        string contentType, long contentLength);
    public static void AddNextPageLink(this SyndicationFeed feed, Uri uri);
    public static void AddPreviousPageLink(this SyndicationFeed feed, Uri uri);
    public static void AddSelfLink(this SyndicationFeed feed, Uri uri);
}

Outre ces méthodes d’extension, l’espace de noms Microsoft.ServiceModel.Web.SpecializedServices contient également un ensemble d’interfaces de contrat de service et de définitions de classes de base pour certains des types les plus courants de services RESTful « spécialisés » (voir la figure 8). Ici, vous trouverez des types pour les services singleton, les services de collection et les services AtomPub.

Interface Classe de base Description

ISingletonService<TItem>

SingletonServiceBase<TItem>

Définit un contrat de service générique et une implémentation de base pour les services REST « singleton », par exemple un service RESTful qui expose uniquement une seule ressource TItem.

ICollectionService<TItem>

CollectionServiceBase<TItem>

Définit un contrat de service générique et une implémentation de base pour les services REST « collection », par exemple un service RESTful qui expose une collection de ressources TItem.

IAtomPubService

AtomPubServiceBase

Définit un contrat de service générique et une implémentation de base pour les services AtomPub.

Figure 8 : Interfaces de contrat de service REST spécialisées et classes de base

Ces types définissent les détails du contrat REST pour chaque type de service, vous protégeant des détails HTTP tout en vous permettant de vous concentrer sur les définitions de ressources (TItem) et la fonctionnalité CRUD principale.

Si vous souhaitez implémenter l’un de ces types de services standard, vous dérivez simplement une classe de la classe de base d’intérêt et la définition de contrat de service correspondante. Ensuite, vous remplacez les méthodes abstraites pour définir la fonctionnalité CRUD pour la ressource en question, puis vous êtes prêt à l’héberger. Vous n’avez pas à vous soucier des détails HTTP, car ils sont gérés par les types de base.

Par exemple, supposons que vous souhaitez implémenter un service de signet simple à l’aide des types de> TItem<> ICollectionService et CollectionServiceBase<TItem>. Pour ce faire, vous pouvez dériver une nouvelle classe à partir des deux types tout en spécifiant Bookmark pour le type de ressource. Ensuite, vous remplacez la poignée de méthodes abstraites définies sur la classe de base, notamment OnAddItem, OnDeleteItem, OnGetItems et OnUpdateItem. Vos implémentations de méthode définissent la fonctionnalité CRUD.

La figure 9 montre un exemple complet d’implémentation pour votre référence.

public class BookmarkService : CollectionServiceBase<Bookmark>,
    ICollectionService<Bookmark>
{
    Dictionary<string, Bookmark> bookmarks = new Dictionary<string, Bookmark>();
    protected override Bookmark OnAddItem(Bookmark initialValue, out string id)
    {
        id = Guid.NewGuid().ToString();
        bookmarks.Add(id, initialValue);
        return initialValue;
    }
    protected override bool OnDeleteItem(string id)
    {
        bookmarks.Remove(id);
        return true;
    }
    protected override Bookmark OnGetItem(string id)
    {
        return bookmarks[id];
    }
    protected override IEnumerable<KeyValuePair<string, Bookmark>> OnGetItems()
    {
        return bookmarks;
    }
    protected override Bookmark OnUpdateItem(string id, Bookmark newValue)
    {
        bookmarks[id] = newValue;
        return bookmarks[id];
    }
}

Figure 9 : Exemple d’implémentation utilisant ICollectionService<TItem> et CollectionServiceBase<TItem>

L’exemple d’implémentation présenté dans la figure 9 est maintenant entièrement prêt à être hébergé. Pour faciliter l’hébergement, l’espace de noms Microsoft.ServiceModel.Web.SpecializedServices contient également une classe d’hôte spécialisée pour chaque type de service spécialisé. Par exemple, vous trouverez des classes SingletonServiceHost, CollectionServiceHost et AtomPubServiceHost à votre disposition. Ces types d’hôtes spécialisés configurent les comportements WCF nécessaires pour vous en arrière-plan afin que vous n’ayez pas à vous en soucier.

L’exemple de code suivant montre comment héberger l’implémentation BookmarkService illustrée dans la figure 9 dans une application console simple :

class Program
{
    static void Main(string[] args)
    {
        CollectionServiceHost host = new CollectionServiceHost(
            typeof(BookmarkService),
            new Uri("https://localhost:8080/bookmarkservice"));
        host.Open();
        Console.WriteLine("Host is up and running...");
        Console.ReadLine();
        host.Close();
    }
}

Si vous exécutez cette application console, puis accédez à l’adresse de base du service, vous devez récupérer la liste des signets (retournés par OnGetItems), comme illustré dans la figure 10.

Figure 10 : Navigation vers BookmarkService pendant son exécution

Et si vous ajoutez « /help » à la fin de l’URI de base, vous verrez la page d’aide REST (voir Et si vous ajoutez « /help » à la fin de l’URI de base, vous verrez la page d’aide REST (voir la figure 11).

Figure 11 : Page d’aide décrivant le service Bookmark

Si vous parcourez la page d’aide, vous verrez que cette implémentation de service prend en charge les formats de message XML et JSON pour chaque opération logique sans aucune action de notre part.

Vous pouvez utiliser ces mêmes techniques pour implémenter des services singleton (un qui n’expose qu’une seule ressource) ou des services AtomPub entièrement fonctionnels, qui sont devenus très populaires dans l’industrie aujourd’hui. Ces types de services spécialisés peuvent faciliter l’exécution de votre service RESTful, en supposant qu’il correspond aux contraintes imposées par l’implémentation générique.

Dans le but de rendre ces types spécialisés encore plus faciles à utiliser, le Kit de démarrage REST WCF est également fourni avec un ensemble de modèles de projet Visual Studio qui aident à démarrer le processus d’utilisation de ces types de service « spécialisés » pour créer de nouvelles implémentations de service.

Modèles de projet Visual Studio

Le Kit de démarrage REST WCF est fourni avec certains modèles de projet Visual Studio utiles qui fournissent le code de plaque de chaudière nécessaire pour quelques types de services « spécialisés ». Une fois que vous avez installé le Kit de démarrage REST WCF, vous verrez une suite de nouveaux modèles de projet dans la boîte de dialogue Nouveau projet Visual Studio (voir la figure 12). J’ai décrit ce que fournit chaque modèle de projet ci-dessous dans la figure 13.

Vous devez simplement en choisir un, entrer les détails restants du projet, puis appuyer sur OK. Ensuite, vous allez finir par un projet REST squelette sur lequel vous pouvez immédiatement exécuter et commencer à générer. Les modèles de projet vous créeront essentiellement une implémentation de service comme celle que je viens de montrer dans la section précédente.

Lorsque vous utilisez l’un de ces modèles de projet, votre principal objectif est de modifier les définitions de classes de ressources et de propager ces modifications tout au long de l’implémentation (vous pouvez utiliser la refactorisation Visual Studio pour y parvenir) et d’implémenter les stubs de méthode CRUD pour chaque opération HTTP.

Passons en revue quelques exemples à l’aide des modèles de projet du Kit de démarrage REST WCF pour vous aider à illustrer comment il peut simplifier le processus de création de ces types de services RESTful.

Figure 12 : Modèles de projet du Kit de démarrage REST WCF

Interface Description

Service WCF de flux Atom

Produit un exemple de service WCF qui montre comment générer et retourner par programme un SyndicationFeed. Vous devez simplement modifier l’implémentation pour remplir le flux avec vos données métier.

AtomPub WCF Service

Produit une implémentation squelette complète pour un service AtomPub entièrement conforme, réduisant considérablement la quantité de code que vous devez écrire pour ce scénario et vous permettant de vous concentrer principalement sur la façon de mapper vos ressources métier au protocole AtomPub.

Service WCF XML BRUT HTTP

Produit un code XML sur le service HTTP qui ne prend pas en charge l’interface HTTP complète. Au lieu de cela, il fournit des opérations GET et POST simples et facilite la création de services XML qui ne sont pas vraiment RESTful.

REST Collection WCF Service

Produit un service qui expose l’interface HTTP complète (GET, POST, PUT et DELETE) autour d’une collection de ressources, et fournit des représentations XML et JSON pour la ressource sous-jacente.

REST Singleton WCF Service

Produit un service qui expose l’interface HTTP complète (GET, POST, PUT et DELETE) autour d’une ressource singleton et fournit des représentations XML et JSON pour la ressource sous-jacente.

Figure 13 : Modèles de projet du kit de démarrage REST WCF

REST Singleton Services

Commençons par créer un service simple qui expose une ressource unique représentant mon emplacement actuel à tout moment. Je vais créer un projet « Service WCF REST Singleton » et le nommer MyWhereabouts. Une fois le projet généré, j’ai un fichier service.svc qui contient mon implémentation de service (le code est réellement trouvé dans service.svc.cs). À ce stade, je peux réellement appuyer sur F5 pour tester le service illustrant le projet généré initial est terminé et prêt à s’exécuter.

Lorsque j’appuie sur F5 pour charger le service et y accéder, le service retourne un <SampleItem> ressource dans une représentation XML, qui est rendue dans le navigateur (voir la figure 14).

Figure 14 : Navigation vers le service singleton généré (aucune modification)

Si vous examinez le code source dans service.svc.cs, vous remarquerez qu’ils ont fourni une définition de classe pour SampleItem : cette classe représente la ressource singleton exposée par l’implémentation du service. L’intention est de modifier cette définition de classe pour représenter la ressource réelle que vous souhaitez exposer. Cette classe est référencée ailleurs dans le fichier afin de tirer parti de la prise en charge de la refactorisation de Visual Studio pour propager la modification une fois que vous l’avez effectuée. Voici à quoi ressemble la classe initiale :

// TODO: Modify the SampleItem. Use Visual Studio refactoring while modifying so
// that the references are updated.
/// <summary>
/// Sample type for the singleton resource.
/// By default all public properties are DataContract serializable
/// </summary>
public class SampleItem
{
    public string Value { get; set; }
}

Et si vous examinez la définition de classe de service, vous verrez qu’elle dérive de SingletonServiceBase<SampleItem> et ISingletonService<SampleItem>. Vous devez maintenant modifier la définition de classe SampleItem en fonction d’un élément plus approprié. Étant donné que j’essaie d’exposer « mon emplacement », je vais remplacer le nom de la classe par MyWhereabouts. Et à droite quand je le fais, j’utilise le menu refactorisation et sélectionnez « Renommer « SampleItem » en « MyWhereabouts » (voir la figure 15).

Figure 15 : Utilisation de visual Studio refactorisation pour modifier le nom de la classe de ressources

La sélection de cette option prend soin de renommer les références de classe dans le reste du fichier afin que tout doit toujours générer et s’exécuter après la modification.

Maintenant, je peux simplement me concentrer sur la modification de la définition de classe pour modéliser la représentation de ressource que j’essaie d’exposer. Pour la ressource MyWhereabouts, j’ajouterai simplement quelques champs publics appelés Placename, Timezone, Lattitude et Longitude. Voici à quoi ressemble ma classe de ressources révisée :

public class MyWhereabouts
{
    public string Placename { get; set; }
    public string Timezone { get; set; }
    public string Lattitude { get; set; }
    public string Longitude { get; set; }
}

Après avoir apporté cette modification, j’ai également modifié le code d’initialisation des champs MyWhereabouts pour définir ces champs sur mon emplacement actuel lors de l’écriture de ce livre blanc :

public class Service : SingletonServiceBase<MyWhereabouts>,
    ISingletonService<MyWhereabouts>
{
    // TODO: This variable used by the sample implementation. Remove if needed
    MyWhereabouts item = new MyWhereabouts()
    {
        Placename = "Fruit Heights",
        Timezone = "GMT-07:00 Mountain Time",
        Lattitude = "41.016962",
        Longitude = "-111.904238"
    };

Avec ces quelques modifications en place, vous pouvez maintenant appuyer à nouveau sur F5 pour accéder au fichier service.svc. Vous devriez voir les <MyWhereabouts> ressource affichée dans le navigateur (voir la figure 16).

Figure 16 : Navigation vers la ressource MyWhereabouts

Cette implémentation de service générique prend en charge l’interface HTTP complète (GET, POST, PUT et DELETE) et chaque opération prend en charge les représentations XML et JSON. Ajoutez simplement « ?format=json » à la fin de l’URI et vous allez récupérer la ressource actuelle au format JSON. Vous pouvez maintenant tester les autres opérations (POST, PUT et DELETE) à l’aide de Fiddler ou en écrivant un client personnalisé.

REST Collection Services

Ensuite, nous allons créer un service de collection REST pour implémenter un autre BookmarkService. Tout d’abord, nous allons créer un projet en sélectionnant le modèle « Service WCF de collection REST ». Comme dans l’exemple précédent, nous allons nous retrouver avec un projet WCF contenant une classe de ressource nommée SampleItem, ainsi qu’une classe de service qui dérive de CollectionServiceBase<SampleItem> et ICollectionService<Bookmark>. Ces types de base implémentent l’interface HTTP complète, comme indiqué précédemment. Maintenant, nous devons apporter seulement quelques modifications.

La première chose que nous devons modifier est le nom de la classe de ressource : nous allons le remplacer de « SampleItem » en « Bookmark » et je tirerai parti de la refactorisation de Visual Studio pour propager à nouveau la modification tout au long du projet. Maintenant, je peux renseigner la classe Bookmark avec les champs dont j’ai besoin pour représenter une ressource de signet. Je vais remplacer la définition de classe Bookmark par ce qui suit :

public class Bookmark
{
    public Uri Url { get; set; }
    public string User { get; set; }
    public string Title { get; set; }
    public string Tags { get; set; }
    public bool Public { get; set; }
    public DateTime LastModified { get; set; }
}

Avec ces modifications en place, mon nouveau service de collection de signets est prêt à être testé. Appuyez simplement sur F5 dans Visual Studio et il charge le service et accédez au fichier service.svc. Lorsque le navigateur apparaît, vous verrez un élément <ItemInfoList vide>, car la collection Bookmark est actuellement vide.

À ce stade, vous pouvez utiliser quelque chose comme Fiddler pour ajouter des signets à la collection (via POST) ou préremplir la collection d’éléments Bookmark dans le constructeur de service. Une fois que vous avez rempli la collection Bookmark, vous obtiendrez quelques éléments <Signet> lorsque vous accédez à nouveau au service (voir la figure 17). Notez que la liste résultante contient des liens vers les ressources Signet individuelles.

Vous pouvez accéder à un signet individuel en suivant l’un des éléments EditLink><trouvés dans l’un des éléments itemInfo <>. Par exemple, vous pouvez obtenir le premier signet de la collection en accédant à «https://localhost:26826/Service.svc/7b5b4a15-3b05-4f94-a7b8-1f324b5cfc7d» (voir la figure 18).

Figure 17 : Navigation vers le service de collection Bookmark

Figure 18 : Navigation vers un signet unique dans la collection

À ce stade, vous pouvez également utiliser des opérations POST, PUT et DELETE sans code supplémentaire : le modèle de projet généré contient déjà une implémentation par défaut pour chacun d’eux. Vous pouvez publier de nouveaux <signets> éléments à l’adresse racine du service et générer une nouvelle ressource Signet et lui attribuer un nouvel ID, puis renvoyer une réponse 201 Créée avec l’en-tête Emplacement correspondant. Vous pouvez également placer <éléments de signet> à des URI de signet individuels pour effectuer des mises à jour. Vous pouvez également envoyer des demandes DELETE à des ressources de signet individuelles pour les supprimer de la collection.

Comme vous pouvez le voir, pour ce type particulier de service RESTful (un service orienté collection), le kit de démarrage REST WCF a permis d’obtenir notre implémentation et de fonctionner avec très peu de codage de notre part.

Atom Feed Services

Lorsque vous devez générer un service de flux Atom simple, créez un projet de type « Service WCF Atom Feed » à partir du kit de démarrage REST WCF. Il génère un service WCF avec un exemple d’implémentation de flux comme celui présenté dans la figure 19. Ce service exécute as-is et produit un exemple de flux Atom qui peut être rendu dans n’importe quel lecteur Atom standard (voir la figure 20 pour voir comment il est rendu dans Internet Explorer).

// TODO: Please set IncludeExceptionDetailInFaults to false in production
// environments
[ServiceBehavior(IncludeExceptionDetailInFaults = true),
 AspNetCompatibilityRequirements(RequirementsMode =
     AspNetCompatibilityRequirementsMode.Allowed), ServiceContract]
public partial class FeedService
{
    // TODO: Modify the URI template and method parameters according to your
    // application. An example URL is http://<url-for-svc-file>?numItems=1
    [WebHelp(Comment = "Sample description for GetFeed.")]
    [WebGet(UriTemplate = "?numItems={i}")]
    [OperationContract]
    public Atom10FeedFormatter GetFeed(int i)
    {
        SyndicationFeed feed;
        // TODO: Change the sample content feed creation logic here
        if (i < 0) throw new WebProtocolException(HttpStatusCode.BadRequest,
            "numItems cannot be negative", null);
        if (i == 0) i = 1;
        // Create the list of syndication items. These correspond to Atom entries
        List<SyndicationItem> items = new List<SyndicationItem>();
        for (int j = 1; j <= i; ++j)
        {
            items.Add(new SyndicationItem()
            {
                // Every entry must have a stable unique URI id
                Id = String.Format(CultureInfo.InvariantCulture,
                    "http://tempuri.org/Id{0}", j),
                Title = new TextSyndicationContent(
                    String.Format("Sample item '{0}'", j)),
                // Every entry should include the last time it was updated
                LastUpdatedTime = new DateTime(
                    2008, 7, 1, 0, 0, 0, DateTimeKind.Utc),
                // The Atom spec requires an author for every entry. If the entry has
                // no author, use the empty string
                Authors =
                {
                    new SyndicationPerson()
                    {
                        Name = "Sample Author"
                    }
                },
                // The content of an Atom entry can be text, xml, a link or arbitrary
                // content. In this sample text content is used.
                Content = new TextSyndicationContent("Sample content"),
            });
        }
        // create the feed containing the syndication items.
        feed = new SyndicationFeed()
        {
            // The feed must have a unique stable URI id
            Id = "http://tempuri.org/FeedId",
            Title = new TextSyndicationContent("Sample feed"),
            Items = items
        };
        feed.AddSelfLink(
            WebOperationContext.Current.IncomingRequest.GetRequestUri());
        #region Sets response content-type for Atom feeds
        WebOperationContext.Current.OutgoingResponse.ContentType = ContentTypes.Atom;
        #endregion
        return feed.GetAtom10Formatter();
    }
}

Figure 20 : Exemple de flux rendu dans Internet Explorer

Ce modèle fournit simplement un exemple de code pour générer un service de flux Atom classique. Vous devez modifier le code pour mapper vos entités métier à l’instance SyndicationFeed, en mappant chacune des entités individuelles à une nouvelle instance SyndicationItem utilisant les champs qu’elle fournit.

AtomPub Services

Lorsque vous souhaitez implémenter un service conforme au protocole de publication Atom, vous devez utiliser le modèle de projet « Service WCF du protocole de publication Atom » fourni avec le kit de démarrage REST WCF WCF. Ce modèle génère un service AtomPub complet qui expose un seul exemple de collection. La classe de service générée dérive de AtomPubServiceBase et IAtomPubService décrits précédemment.

Vous pouvez tester le service immédiatement en accédant au fichier service.svc et le service retourne un document de service AtomPub décrivant les collections qu’il prend en charge (voir la figure 21). Comme vous pouvez le voir, ce service expose une collection appelée « Sample Collection » accessible en ajoutant « collection1 » à la fin de l’URL racine du service. Lorsque vous accédez à la collection, le service retourne un flux Atom représentant l’exemple de collection (voir la figure 22). Le service prend également en charge l’ajout, la mise à jour et la suppression d’entrées Atom via l’interface HTTP AtomPub standard.

Lorsque vous générez des services AtomPub à l’aide du Kit de démarrage REST WCF, votre travail consiste à vous concentrer sur les collections logiques que vous souhaitez exposer. Vous devez définir un mappage entre vos collections d’entités métier et les collections AtomPub exposées par le service, ce qui se résume essentiellement à définir un mappage entre vos classes d’entités métier personnalisées et les classes WCF SyndicationFeed/Item.

Figure 21 : Navigation vers le service AtomPub

Figure 22 : Exploration de l’exemple de collection exposé par le service AtomPub

HTTP Plain XML Services

Dans les cas où vous n’avez pas vraiment besoin ou que vous souhaitez effectuer un service ENTIÈREMENT RESTful (respectant toutes les contraintes REST et prenant en charge l’interface HTTP complète), mais que vous préférez régler avec un service XML-over-HTTP simple, vous ne souhaitez pas utiliser les modèles de projet que nous venons de couvrir. Au lieu de cela, vous souhaiterez consulter le modèle de projet « Service WCF XML BRUT HTTP », qui ne tente pas de fournir une implémentation RESTful. Au lieu de cela, il fournit quelques exemples d’opérations XML-over-HTTP pour vous aider à commencer.

La figure 23 montre l’exemple d’implémentation que vous obtiendrez lorsque vous utilisez ce modèle de projet. Notez comment il fournit une opération [WebGet] qui accepte certains paramètres de chaîne de requête comme entrée, et fournit une autre opération [WebInvoke] qui accepte et retourne un corps d’entité XML.  Ces opérations sont simplement fournies en tant qu’exemples pour vous montrer comment commencer à utiliser les anciens services XML bruts (POX).

Si vous essayez de créer un service POX qui retourne des données, vous pouvez conserver la méthode GetData et ajuster les données de requête/réponse en fonction de vos besoins. Si vous avez besoin de prendre en charge les requêtes POST, vous pouvez conserver la méthode DoWork et ajuster en conséquence. Les chances sont que vous finirez par réécrire ces méthodes dans leur intégralité afin que ce modèle de projet particulier ne fournisse pas autant de valeur que certains des autres.

[ServiceBehavior(IncludeExceptionDetailInFaults = true),
 AspNetCompatibilityRequirements(RequirementsMode =
     AspNetCompatibilityRequirementsMode.Allowed), ServiceContract]
public partial class Service
{
    [WebHelp(Comment = "Sample description for GetData")]
    [WebGet(UriTemplate = "GetData?param1={i}&param2={s}")]
    [OperationContract]
    public SampleResponseBody GetData(int i, string s)
    {
        // TODO: Change the sample implementation here
        if (i < 0) throw new WebProtocolException(HttpStatusCode.BadRequest,
            "param1 cannot be negative", null);
        return new SampleResponseBody()
        {
            Value = String.Format("Sample GetData response: '{0}', '{1}'", i, s)
        };
    }
    [WebHelp(Comment = "Sample description for DoWork")]
    [WebInvoke(UriTemplate = "DoWork")]
    [OperationContract]
    public SampleResponseBody DoWork(SampleRequestBody request)
    {
        //TODO: Change the sample implementation here
        return new SampleResponseBody()
        {
            Value = String.Format("Sample DoWork response: '{0}'", request.Data)
        };
    }
}

Figure 23 : Exemple d’implémentation du service WCF XML BRUT HTTP

Consommation de services RESTful avec HttpClient

L’un des aspects les plus difficiles de l’utilisation des services RESTful consiste à écrire le code côté client pour les consommer. Étant donné que les services RESTful ne fournissent pas de métadonnées de type WSDL, les développeurs côté client ne bénéficient pas du luxe des classes proxy de génération de code et fortement typées qui facilitent l’intégration par programmation de la plupart des services SOAP. Cette réalité conduit souvent les développeurs à penser que l’intégration avec les services RESTful est plus fastidieuse que les services SOAP classiques. À mon avis, c’est en grande partie une question de perspective. Ils choisissent l’API HTTP côté client à programmer.

Étant donné que les services REST sont simplement des services HTTP, vous pouvez littéralement utiliser n’importe quelle API HTTP pour les consommer. Cela vous offre une grande flexibilité côté client. Microsoft .NET fournit les classes System.Net, telles que WebRequest et WebResponse, pour la programmation du code client HTTP. Ces classes effectueront le travail, mais elles rendent l’expérience côté client plus complexe qu’elle ne le devrait être.  Les développeurs utilisés pour travailler avec des proxys BASÉS sur SOAP ne le trouvent généralement pas très attrayant ou naturel.

Dans un effort de simplification de l’expérience de programmation côté client pour l’utilisation des services RESTful, le kit de démarrage REST WCF (préversion 2) est fourni avec une nouvelle API HTTP appelée HttpClient qui fournit un modèle plus naturel pour la programmation de l’interface HTTP uniforme, ainsi que de nombreuses méthodes d’extension qui facilitent la gestion de différents types de contenu trouvés dans les messages HTTP.

Prise en main de HttpClient

La classe HttpClient fournit une API simple pour l’envoi de requêtes HTTP et le traitement des réponses HTTP. La fonctionnalité est définie dans deux définitions de classe primaires : l’une appelée HttpClient et une autre appelée HttpMethodExtensions (voir la figure 24). L’ancien définit les fonctionnalités de base de la classe tandis que celui-ci fournit un ensemble de méthodes d’extension en couche ciblant les différentes méthodes HTTP logiques.

Si vous inspectez la classe HttpClient, vous verrez qu’elle fournit un moyen de spécifique l’adresse de base cible, un moyen de manipuler les en-têtes de requête HTTP et de nombreuses surcharges pour envoyer la requête. Vous fournissez le contenu du message de requête via une instance HttpContent et vous traitez la réponse via l’objet HttpResponseMessage que vous récupérez. La classe fournit également des méthodes d’envoi asynchrones pour les situations où vous ne souhaitez pas bloquer le thread appelant en attendant que la réponse revienne.

La classe HttpMethodExtensions facilite encore davantage l’ajout de plusieurs méthodes d’extension à HttpClient pour émettre des requêtes logiques Get, Post, Put et Delete. Il s’agit des méthodes que vous utiliserez probablement lors de l’utilisation d’un service RESTful avec HttpClient.

public class HttpClient : IDisposable
{
    public HttpClient();
    public HttpClient(string baseAddress);
    public HttpClient(Uri baseAddress);
    public Uri BaseAddress { get; set; }
    public RequestHeaders DefaultHeaders { get; set; }
    public IList<HttpStage> Stages { get; set; }
    public HttpWebRequestTransportSettings TransportSettings { get; set; }
    public event EventHandler<SendCompletedEventArgs> SendCompleted;
    public IAsyncResult BeginSend(HttpRequestMessage request,
        AsyncCallback callback, object state);
    protected virtual HttpStage CreateTransportStage();
    public void Dispose();
    protected virtual void Dispose(bool disposing);
    public HttpResponseMessage EndSend(IAsyncResult result);
    public HttpResponseMessage Send(HttpMethod method);
    public HttpResponseMessage Send(HttpRequestMessage request);
    public HttpResponseMessage Send(HttpMethod method, string uri);
    public HttpResponseMessage Send(HttpMethod method, Uri uri);
    public HttpResponseMessage Send(HttpMethod method, string uri,
        HttpContent content);
    public HttpResponseMessage Send(HttpMethod method, string uri,
        RequestHeaders headers);
    public HttpResponseMessage Send(HttpMethod method, Uri uri, HttpContent content);
    public HttpResponseMessage Send(HttpMethod method, Uri uri,
        RequestHeaders headers);
    public HttpResponseMessage Send(HttpMethod method, string uri,
        RequestHeaders headers, HttpContent content);
    public HttpResponseMessage Send(HttpMethod method, Uri uri,
        RequestHeaders headers, HttpContent content);
    public void SendAsync(HttpRequestMessage request);
    public void SendAsync(HttpRequestMessage request, object userState);
    public void SendAsyncCancel(object userState);
    protected void ThrowIfDisposed();
}
public static class HttpMethodExtensions
{
    public static HttpResponseMessage Delete(this HttpClient client, string uri);
    public static HttpResponseMessage Delete(this HttpClient client, Uri uri);
    public static HttpResponseMessage Get(this HttpClient client);
    public static HttpResponseMessage Get(this HttpClient client, string uri);
    public static HttpResponseMessage Get(this HttpClient client, Uri uri);
    public static HttpResponseMessage Get(this HttpClient client, Uri uri,
        HttpQueryString queryString);
    public static HttpResponseMessage Get(this HttpClient client, Uri uri,
        IEnumerable<KeyValuePair<string, string>> queryString);
    public static HttpResponseMessage Head(this HttpClient client, string uri);
    public static HttpResponseMessage Head(this HttpClient client, Uri uri);
    public static HttpResponseMessage Post(this HttpClient client, string uri,
        HttpContent body);
    public static HttpResponseMessage Post(this HttpClient client, Uri uri,
        HttpContent body);
    public static HttpResponseMessage Post(this HttpClient client, string uri,
        string contentType, HttpContent body);
    public static HttpResponseMessage Post(this HttpClient client, Uri uri,
        string contentType, HttpContent body);
    public static HttpResponseMessage Put(this HttpClient client, string uri,
        HttpContent body);
    public static HttpResponseMessage Put(this HttpClient client, Uri uri,
        HttpContent body);
    public static HttpResponseMessage Put(this HttpClient client, string uri,
        string contentType, HttpContent body);
    public static HttpResponseMessage Put(this HttpClient client, Uri uri,
        string contentType, HttpContent body);
}

Figure 24 : Définitions de classes HttpClient et HttpMethodExtensions

Examinons un exemple pour voir comment HttpClient simplifie les choses. Nous allons consommer un service RESTful réel trouvé sur le Web qui n’a pas été créé avec .NET. Nous allons utiliser l’API REST Twitter.

Si vous accédez à la documentation de l’API REST Twitter à http://apiwiki.twitter.com/Twitter-API-Documentation, vous pouvez rapidement apprendre à émettre les requêtes HTTP appropriées à intégrer au service. Le code suivant montre comment récupérer la « chronologie de l’ami » d’un utilisateur Twitter :

HttpClient http = new HttpClient("http://twitter.com/statuses/");
http.TransportSettings.Credentials =
    new NetworkCredential("{username}", "{password}");
HttpResponseMessage resp = http.Get("friends_timeline.xml");
resp.EnsureStatusIsSuccessful();
ProcessStatuses(resp.Content.ReadAsStream());

Lorsque nous construisons l’instance HttpClient, nous fournissons l’adresse de base du service d’API REST Twitter, puis nous fournissons les informations d’identification HTTP de l’utilisateur via la propriété TransportSettings.Credentials.  Nous sommes maintenant prêts à utiliser les différentes méthodes Get, Post, Put et Delete pour interagir avec les différentes ressources exposées par le service. Dans cet exemple, j’appelle Get pour récupérer la ressource «friends_timeline.xml» à partir du service. Une fois cette opération retournée, je peux appeler EnsureStatusIsSuccessful sur l’objet resp pour vous assurer que nous avons obtenu un code d’état HTTP de niveau 200.

Ensuite, nous devons traiter le contenu du message de réponse. Une façon d’accomplir cela consiste à lire la réponse en tant que flux, puis à traiter le flux (ReadAsStream) avec votre API XML préférée. Voici un exemple qui montre comment traiter le code XML de réponse à l’aide de XmlDocument :

static void ProcessStatuses(Stream str)
{
    XmlDocument doc = new XmlDocument();
    doc.Load(str);
    XmlNodeList statuses = doc.SelectNodes("/statuses/status");
    foreach (XmlNode n in statuses)
        Console.WriteLine("{0}: {1}",
            n.SelectSingleNode("user/screen_name").InnerText,
            n.SelectSingleNode("text").InnerText);
}

La classe HttpClient est fournie avec des fonctionnalités permettant de traiter le contenu du message en tant que flux, chaîne ou tableau d’octets. En plus de cette fonctionnalité de base, le Kit de démarrage REST WCF (préversion 2) est fourni avec un autre assembly appelé Microsoft.Http.Extensions qui contient de nombreuses méthodes d’extension qui permettent de traiter le contenu du message à l’aide d’autres techniques populaires (XLinq, XmlSerializer, SyndicationFeed, etc.). Nous allons voir comment fonctionnent ces méthodes d’extension dans la section suivante.

Examinons un autre exemple montrant comment mettre à jour l’état Twitter d’un utilisateur. Vous pouvez effectuer cette opération avec HttpClient à l’aide du code suivant (en supposant que HttpClient est déjà instancié ci-dessus) :

HttpUrlEncodedForm form = new HttpUrlEncodedForm();
form.Add("status", "my first HttpClient app");
resp = http.Post("update.xml", form.CreateHttpContent());
resp.EnsureStatusIsSuccessful();

La ressource «update.xml» nécessite une requête POST et attend que le message contienne une chaîne encodée url contenant le nouveau texte d’état. HttpClient facilite la génération d’un message encodé par URL via la classe HttpUrlEncodedForm, puis vous appelez simplement Post en fournissant le contenu.

Cet exemple rapide illustre la facilité à émettre des requêtes GET et POST à l’aide de HttpClient. Dans les sections suivantes, nous allons approfondir les détails de HttpClient et mettre en évidence certaines de ses principales fonctionnalités.

Traitement du contenu des messages

En règle générale, vous souhaiterez traiter le corps de la réponse HTTP à l’aide d’un élément un peu plus sophistiqué qu’un flux. Le kit de démarrage REST WCF (préversion 2) est fourni avec un autre assembly appelé Microsoft.Http.Extensions qui améliore la classe HttpContent avec de nombreuses méthodes d’extension qui permettent de traiter le contenu du message à l’aide d’une API spécifique.

Dans la version actuelle, ces méthodes d’extension sont réparties entre plusieurs espaces de noms. Il est important de noter que vous ne les verrez pas dans IntelliSense, sauf si vous avez ajouté une référence à Microsoft.Http.Extensions.dllet que vous avez ajouté une instruction using à l’espace de noms approprié. La figure 25 répertorie les méthodes d’extension HttpContent mises à disposition par cet assembly supplémentaire.

Extension, méthode Namespace

ReadAsDataContract

System.Runtime.Serialization

ReadAsJsonDataContract

System.Runtime.Serialization.Json

ReadAsServiceDocument

System.ServiceModel.Syndication

ReadAsSyndicationFeed

System.ServiceModel.Syndication

ReadAsXmlReader

System.Xml

ReadAsXElement

System.Xml.Linq

ReadAsXmlSerializable

System.Xml.Serialization

Figure 25 : Méthodes d’extension HttpContent

Examinons quelques exemples qui montrent comment utiliser certaines de ces méthodes d’extension. Nous allons commencer par un exemple XLinq, car il s’agit d’un choix commun de programmation XML .NET aujourd’hui. Au lieu d’appeler ReadAsStream sur la propriété Content, nous allons maintenant appeler ReadAsXElement comme illustré ici :

HttpResponseMessage resp = http.Get("friends_timeline.xml");
resp.EnsureStatusIsSuccessful();
ProcessStatusesAsXElement(resp.Content.ReadAsXElement());

N’oubliez pas que vous aurez besoin d’une référence à l’assembly Microsoft.Http.Extensions et vous devrez ajouter une instruction using au fichier pour System.Xml.Linq . En supposant que vous avez effectué ces deux étapes, vous devriez voir ReadAsXElement dans IntelliSense sur la propriété De contenu. L’exemple suivant montre comment traiter le contenu du message en tant que XElement :

static void ProcessStatusesAsXElement(XElement root)
{
    var statuses = root.Descendants("status");
    foreach (XElement status in statuses)
        Console.WriteLine("{0}: {1}",
            status.Element("user").Element("screen_name").Value,
            status.Element("text"));
}

Il serait très similaire si vous vouliez traiter le contenu du message à l’aide de XmlReader au lieu de XElement : vous appelez simplement ReadAsXmlReader et traitez le contenu en conséquence.

Au lieu d’utiliser directement l’API XML, de nombreux développeurs préfèrent tirer parti des techniques de sérialisation XML, ce qui leur permet de travailler avec des types .NET fortement typés au lieu de nœuds XML génériques. Les méthodes d’extension ReadAsDataContract et ReadAsJsonDataContract vous permettent de tirer parti du DataContractSerializer (pour le contenu XML ou JSON respectivement) pour la sérialisation tandis que ReadAsXmlSerializable vous permet de tirer parti du moteur de sérialisation XmlSerializer.

Toutefois, avant de pouvoir utiliser l’une de ces méthodes basées sur la sérialisation, vous devez acquérir certaines classes .NET qui modélisent correctement le contenu du message que vous allez traiter. Il peut être possible de générer les classes appropriées à partir d’une définition de schéma XML fournie par le service ou d’un exemple de code XML que vous pouvez récupérer lors de l’utilisation du service pendant le développement. Mais si tout autre problème échoue, vous pouvez toujours créer ces types manuellement en fonction de vos connaissances du format XML.

Par exemple, voici quelques classes C# qui modélisent les états que nous revenons de Twitter ci-dessus lors de l’utilisation du DataContractSerializer pour effectuer la désérialisation :

[assembly: ContractNamespace("", ClrNamespace = "TwitterShell")]
[CollectionDataContract(Name = "statuses", ItemName = "status")]
public class statusList : List<status> { }
public class user
{
    public string id;
    public string name;
    public string screen_name;
}
public class status
{
    public string id;
    public string text;
    public user user;
}

Avec ces classes en place, nous pouvons maintenant utiliser la méthode ReadAsDataContract spécifiant le type racine à utiliser pendant le processus de désérialisation (dans ce cas, statusList) :

HttpResponseMessage resp = http.Get("friends_timeline.xml");
resp.EnsureStatusIsSuccessful();
ProcessStatusesAsDataContract(resp.Content.ReadAsDataContract<statusList>());

Maintenant que nous avons le contenu du message désérialisé dans un objet statusList, le code de traitement devient beaucoup plus simple que vous pouvez le voir ici :

static void ProcessStatusesAsDataContract(statusList list)
{
    foreach (status status in list)
        Console.WriteLine("{0}: {1}", status.user.screen_name, status.text);
}

Si la réponse HTTP est retournée au format JSON (au lieu de XML), vous pouvez simplement appeler ReadAsJsonDataContract comme illustré ici :

HttpResponseMessage resp = http.Get("friends_timeline.json");
resp.EnsureStatusIsSuccessful();
ProcessStatusesAsDataContract(resp.Content.ReadAsJsonDataContract<statusList>());

Le reste du code de traitement reste le même.

En outre, si vous souhaitez utiliser le moteur XmlSerializer, vous devez d’abord vous assurer que vous disposez de types sérialisables compatibles avec le mappage XmlSerializer. Pour cet exemple particulier, nous devons remplacer le type statusList par le type racine suivant (car le mappage XML est différent) :

public class statuses
{
    [XmlElement("status")]
    public status[] status;
}

Ensuite, vous appelez simplement ReadAsXmlSerializable au lieu de ReadAsDataContract, en spécifiant les états du type racine pour désérialiser.  En général, XmlSerializer est plus flexible que DataContractSerializer en termes de xml qu’il est en mesure de gérer. Par conséquent, il est probable que XmlSerializer devienne un choix plus courant pour consommer des services RESTful. De plus, le kit de démarrage REST WCF (préversion 2) est fourni avec un plug-in Visual Studio qui facilite la génération de types XmlSerializer. Nous allons aborder le fonctionnement dans la section suivante.

En guise de dernier exemple, que se passe-t-il si le service retourne un flux Atom ? Dans ce cas, vous pouvez simplement appeler ReadAsSyndicationFeed, puis vous récupérerez un objet SyndicationFeed pour traiter :

HttpResponseMessage resp = http.Get("friends_timeline.atom");
resp.EnsureStatusIsSuccessful();
ProcessStatusesAsFeed(resp.Content.ReadAsSyndicationFeed());

Vous pouvez ensuite traiter l’objet SyndicationFeed pour extraire les informations d’intérêt (vous devez savoir où se trouvent les informations dans la structure de flux Atom) :

static void ProcessStatusesAsFeed(SyndicationFeed feed)
{
    foreach (SyndicationItem item in feed.Items)
        Console.WriteLine("{0}: {1}", item.Authors[0].Name, item.Title.Text);
}

Comme vous pouvez le voir, la nouvelle classe HttpClient facilite vraiment l’utilisation des services RESTful dans .NET. Les nouvelles méthodes d’extension trouvées dans Microsoft.Http.Extensions offrent une grande flexibilité autour du traitement des messages et simplifient certains des scénarios REST les plus courants (XML, JSON et Atom, etc.).

« Coller du code XML en tant que types » dans Visual Studio

Étant donné que les services RESTful ne sont pas fournis avec WSDL, il n’est pas possible de générer des proxys fortement typés comme vous êtes utilisé avec SOAP. Toutefois, étant donné que tous les services REST implémentent le même contrat de service (l’interface uniforme HTTP), vous n’avez pas vraiment besoin de quoi que ce soit au-delà des opérations fournies par HttpClient (Get, Post, Put et Delete). Toutefois, vous souhaiterez peut-être que certains types sérialisables simplifient la logique de traitement des messages HTTP, que je vous ai montré comment effectuer dans la section précédente.

Si le service en question fournit une définition de schéma XML décrivant le contenu (comme la page « aide » automatique fournit le kit de démarrage REST WCF côté service), vous pouvez utiliser des outils tels que xsd.exe ou svcutil.exe pour générer les types appropriés. Si ce n’est pas le cas, vous pouvez tirer parti d’un nouveau plug-in Visual Studio introduit par le kit de démarrage REST WCF (préversion 2) appelé « Coller xml en tant que types ».

Cette nouvelle fonctionnalité vous permet de copier une définition de schéma XML ou un exemple d’instance XML dans votre Presse-papiers, puis vous pouvez sélectionner « Coller xml en tant que types » dans le menu Modifier. Lorsque vous procédez ainsi, il génère les types XmlSerializer appropriés pour le schéma/XML dans le Presse-papiers et les colle à l’emplacement actuel dans le fichier. Lorsque vous utilisez un exemple d’instance XML, il ne sera pas toujours en mesure de produire des types parfaitement précis. Vous devrez peut-être effectuer des massages supplémentaires après la génération.

Voyons comment nous pourrions l’utiliser avec notre exemple Twitter. Si vous accédez à la ressource twitter «friends_timeline.xml» à l’aide de votre navigateur (http://twitter.com/statuses/friends\_timeline.xml), vous récupérerez le code XML réel retourné par cette ressource (voir la figure 26). Effectuez maintenant une opération « Afficher la source » et copiez le code XML dans votre Presse-papiers. Une fois copié, vous pouvez revenir à Visual Studio et positionner votre curseur où nous voulons que les types générés soient activés. Sélectionnez ensuite simplement « Coller xml en tant que types » dans le menu Modifier (voir la figure 27) et les types XmlSerializer requis seront ajoutés au fichier. Une fois en place, vous pouvez utiliser ces types conjointement avec ReadAsXmlSerializable pour traiter le contenu du message.

Vous pouvez utiliser cette fonctionnalité conjointement avec la page « aide » fournie par le kit de démarrage REST WCF lors de l’implémentation de services. Vous pouvez parcourir la page d’aide pendant le développement, accéder aux schémas ou exemples de messages XML pour les différentes ressources et opérations, et vous pouvez utiliser ce plug-in pour générer les types de messages appropriés dans votre application cliente. En général, cela réduit la barre pour les clients qui tentent d’intégrer des services RESTful et simplifie l’expérience du développeur.

Figure 26 : Exemple de code XML retourné par la ressource de friends_timeline.xml Twitter

Figure 27 : Coller du code XML en tant qu’élément de menu Types

Gestion des entrées de service

La section précédente s’est concentrée sur le traitement du contenu du message trouvé dans les messages de réponse HTTP. Il est également important de déterminer comment générer l’entrée appropriée attendue par un service RESTful. De nombreux services acceptent l’entrée sous la forme de chaînes de requête, de données de formulaire encodées d’URL ou via divers autres formats que vous pouvez utiliser dans le corps de l’entité de requête HTTP (par exemple, XML, JSON, Atom, etc.).

Si le service nécessite une entrée de chaîne de requête, vous pouvez utiliser la classe HttpQueryString pour la générer correctement. L’exemple suivant montre comment générer une chaîne de requête que nous pouvons fournir à la méthode Get :

HttpQueryString vars = new HttpQueryString();
vars.Add("id", screenname);
vars.Add("count", count);
resp = http.Get(new Uri("user_timeline.xml", UriKind.Relative), vars);
resp.EnsureStatusIsSuccessful();
DisplayTwitterStatuses(resp.Content.ReadAsXElement());

Si le service nécessite une entrée de formulaire encodée par URL (par exemple, ce que vous obtiendriez à partir d’un formulaire HTML <> soumission), vous pouvez utiliser la classe HttpUrlEncodedForm ou HttpMultipartMimeForm pour générer le contenu approprié (utilisez la version ultérieure si vous devez produire un formulaire MIME en plusieurs parties). L’exemple suivant montre comment créer une soumission de formulaire simple que nous pouvons fournir à la méthode Post :

HttpUrlEncodedForm form = new HttpUrlEncodedForm();
form.Add("status", status);
resp = http.Post("update.xml", form.CreateHttpContent());
resp.EnsureStatusIsSuccessful();
Console.WriteLine("Status updated!");

En plus de ces classes, l’assembly Microsoft.Http.Extensions est fourni avec certaines méthodes d’extension supplémentaires qui simplifient le processus de génération d’objets HttpContent que vous pouvez fournir à HttpClient en tant qu’entrée. Ces méthodes sont définies dans la classe HttpContentExtensions et incluent des éléments tels que Create (à partir d’un XElement), CreateDataContract, CreateJsonDataContract, CreateXmlSerializable, CreateAtom10SyndicationFeed et CreateRss20SyndicationFeed. Vous devez utiliser ces méthodes lorsque vous devez générer l’un de ces types de contenu pour le message de requête HTTP.

Simplification du traitement des en-têtes avec des en-têtes typés

L’assembly Microsoft.Http est également fourni avec une suite de classes d’en-tête HTTP fortement typées. Vous les trouverez dans l’espace de noms Microsoft.Http.Headers. Par exemple, vous trouverez des classes telles que CacheControl, Connection, Cookie, Credential, EntityTag, Expect, ainsi que d’autres.

La classe HttpClient fournit une propriété DefaultHeaders qui expose les en-têtes de requête à vous via ces types d’en-têtes. Vous pouvez manipuler des en-têtes individuels avant d’envoyer la requête. La classe HttpResponseMessage est également fournie avec une propriété Headers qui expose les différents en-têtes de réponse HTTP à vous, à nouveau via ces classes d’en-tête.  L’exemple suivant montre comment manipuler des en-têtes dans la requête/réponse afin d’émettre une requête GET conditionnelle :

HttpResponseMessage resp = http.Get("public_timeline.atom");
resp.EnsureStatusIsSuccessful();
ProcessStatusesAsFeed(resp.Content.ReadAsSyndicationFeed());
DateTime? lastAccessDate = resp.Headers.Date;
...
http.DefaultHeaders.IfModifiedSince = lastAccessDate;
resp = http.Get("public_timeline.atom");
Console.WriteLine("status={0}", resp.StatusCode);

Ces classes d’en-tête HTTP fortement typées facilitent l’utilisation des en-têtes HTTP dans votre code, car elles vous protègent de nombreux aspects des détails du protocole HTTP sous-jacents.

Traitement httpClient « étape »

La classe HttpClient est fournie avec un mécanisme d’interception de requête dans lequel vous pouvez connecter du code personnalisé, similaire au modèle RequestInterceptor fourni par WebServiceHost2 côté service. Ce modèle d’interception de requête est conçu spécifiquement pour les interactions HTTP côté client.

Voici comment cela fonctionne. La classe HttpClient gère une collection de « phases de traitement HTTP », qui sont modélisées par la classe HttpStage. Avant d’envoyer des messages, vous configurez une collection d’objets dérivés de HttpStage avec votre instance HttpClient. Ensuite, chaque fois que vous émettez une requête HTTP, l’instance HttpClient appelle chaque objet HttpStage, ce qui lui donne la possibilité d’effectuer son traitement.

Il existe quelques classes qui dérivent de HttpStage, HttpProcessingStage et HttpAsyncStage, qui fournissent respectivement des modèles synchrones et asynchrones. Vous dériverez généralement de l’une de ces deux classes lors de l’implémentation d’une étape personnalisée de votre propre. Lorsque vous dérivez de ces classes, il s’agit de votre travail de remplacer les méthodes ProcessRequest et ProcessResponse pour définir votre logique.

Le code suivant montre comment implémenter un HttpProcessingStage personnalisé qui imprime simplement un message dans la fenêtre de console :

public class MyHttpStage : HttpProcessingStage
{
    public override void ProcessRequest(HttpRequestMessage request)
    {
        Console.WriteLine("ProcessRequest called: {0} {1}",
            request.Method, request.Uri);
    }
    public override void ProcessResponse(HttpResponseMessage response)
    {
        Console.WriteLine("ProcessResponse called: {0}",
            response.StatusCode);
    }
}

Une fois que vous avez implémenté votre classe dérivée de HttpStage personnalisée, vous pouvez en tirer parti lors de l’utilisation de HttpClient. L’exemple suivant montre comment ajouter une instance MyHttpStage dans la combinaison avant d’émettre des requêtes HTTP :

HttpClient http = new HttpClient("http://twitter.com/statuses/");
http.TransportSettings.Credentials =
    new NetworkCredential("skonnarddemo", "baby95");
// configure the custom stage
http.Stages.Add(new MyHttpStage());
HttpResponseMessage resp = http.Get("public_timeline.atom");

À présent, lorsque vous appelez la méthode Get, vous verrez les messages MyHttpStage imprimés dans la fenêtre de console avant et après avoir émis la requête HTTP au service cible. Vous pouvez utiliser cette technique d’interception pour implémenter un large éventail de besoins de traitement HTTP côté client (par exemple, sécurité, journalisation, suivi, mise en cache personnalisée, etc.) afin de favoriser une plus grande réutilisation dans les applications clientes HTTP.

Extension de HttpClient pour créer des clients spécialisés

En plus du mécanisme d’extensibilité HttpStage, il est également possible de dériver de HttpClient pour créer vos propres bibliothèques de client REST spécialisées. Cela vous permet de fournir des méthodes et des propriétés qui seront plus logiques pour les utilisateurs de votre service RESTful et peuvent en fin de compte fournir une expérience de développement qui parallèle (en aucun cas) l’expérience de classe proxy SOAP classique.

Dans le cadre du kit de démarrage REST WCF (préversion 2), ils fournissent un exemple de classe dérivée httpclient spécialisée appelée AtomPubClient. Il offre une expérience personnalisée côté client pour interagir avec les services AtomPub standard. Voici à quoi ressemble la définition de classe AtomPubClient :

public class AtomPubClient : HttpClient
{
    public AtomPubClient();
    public SyndicationItem AddEntry(SyndicationFeed feed, SyndicationItem newEntry);
    public SyndicationItem AddEntry(Uri feedUri, SyndicationItem newEntry);
    public SyndicationItem AddMediaResource(SyndicationFeed feed, string contentType,
        string description, HttpContent mediaContent);
    public SyndicationItem AddMediaResource(Uri mediaCollectionUri,
        string contentType, string description, HttpContent mediaContent);
    public void DeleteEntry(SyndicationItem entry);
    public void DeleteEntry(Uri itemUri);
    public SyndicationItem GetEntry(Uri itemUri);
    public SyndicationFeed GetFeed(Uri feedUri);
    public ServiceDocument GetServiceDocument(Uri serviceDocumentUri);
    public SyndicationItem UpdateEntry(SyndicationItem oldValue,
        SyndicationItem newValue);
    public SyndicationItem UpdateEntry(Uri editUri, SyndicationItem newValue);
}

Notez comment il fournit des méthodes telles que AddEntry, GetEntry, UpdateEntry, GetFeed, etc qui sont spécifiques à un service AtomPub, qui est un moyen beaucoup plus naturel d’interagir avec ces types de services que d’utiliser les méthodes Get, Post, Put et Delete sous-jacentes sur HttpClient.

L’exemple de code suivant montre comment utiliser la classe AtomPubClient pour naviguer dans un service AtomPub et ajouter une nouvelle entrée Atom dans la première collection d’espaces de travail :

AtomPubClient client = new AtomPubClient();
ServiceDocument doc = client.GetServiceDocument(
    new Uri("https://localhost:30807/Service.svc/"));
Uri feedUri = doc.Workspaces[0].Collections[0].Link;
SyndicationFeed feed = client.GetFeed(feedUri);
SyndicationItem item = new SyndicationItem()
{
    Title = new TextSyndicationContent("New Item"),
    PublishDate = DateTime.Now
};
client.AddEntry(feed, item);

Le kit de démarrage REST WCF (préversion 2) est également fourni avec une classe appelée PollingAgent qui facilite l’implémentation de la logique cliente pour « interroger » une ressource de service et de faire quelque chose uniquement lorsque la ressource change. Vous utilisez PollingAgent conjointement avec un objet HttpClient qui effectue réellement le travail HTTP. Voici la définition de classe complète pour PollingAgent :

public class PollingAgent : IDisposable
{
    public PollingAgent();
    public HttpClient HttpClient { get; set; }
    public bool IgnoreExpiresHeader { get; set; }
    public bool IgnoreNonOKStatusCodes { get; set; }
    public bool IgnoreSendErrors { get; set; }
    public TimeSpan PollingInterval { get; set; }
    public event EventHandler<ConditionalGetEventArgs> ResourceChanged;
    public void Dispose();
    public void StartPolling();
    public void StartPolling(Uri uri);
    public void StartPolling(Uri uri, EntityTag etag, DateTime? lastModifiedTime);
    public void StopPolling();
}

Vous créez donc une instance de la classe PollingAgent et fournissez-la à un objet HttpClient à utiliser. Ensuite, vous établissez la méthode de rappel pour l’événement ResourceChanged et spécifiez l’intervalle d’interrogation. Une fois que vous avez tout cela en place, vous appelez simplement StartPolling et fournissez l’URI de ressource cible. L’exemple de code suivant montre comment configurer ceci pour « interroger » la chronologie publique Twitter :

class Program
{
    static void Main(string[] args)
    {
        PollingAgent pollingClient = new PollingAgent();
        pollingClient.HttpClient = new HttpClient();
        pollingClient.HttpClient.TransportSettings.Credentials =
            new NetworkCredential("skonnarddemo", "baby95");
        pollingClient.PollingInterval = TimeSpan.FromSeconds(10);
        pollingClient.ResourceChanged += new EventHandler<ConditionalGetEventArgs>(
            pollingClient_ResourceChanged);
        pollingClient.StartPolling(
            new Uri("http://twitter.com/statuses/public_timeline.xml"));
        Console.WriteLine("polling...");
        Console.ReadLine();
    }
    static void pollingClient_ResourceChanged(object s, ConditionalGetEventArgs e)
    {
        ProcessStatusesAsXElement(e.Response.Content.ReadAsXElement());
    }
    static void ProcessStatusesAsXElement(XElement root)
    {
        var statuses = root.Descendants("status");
        foreach (XElement status in statuses)
            Console.WriteLine("{0}: {1}",
                status.Element("user").Element("screen_name").Value,
                status.Element("text"));
    }
}

Comme vous pouvez le voir à partir de ces exemples, HttpClient fournit une base HTTP simple que vous pouvez étendre pour fournir des modèles de programmation côté client plus spécialisés. Investir un certain temps dans la création de vos propres classes dérivées de HttpClient paie des dividendes importants en termes d’expérience de développement côté client.

Conclusion

Microsoft s’efforce de fournir un modèle de programmation de première classe pour l’implémentation et la consommation de services RESTful à l’aide de Microsoft .NET Framework. WCF 3.5 a introduit le modèle de programmation « Web » fondamental requis pour générer des services RESTful, mais il ne s’agissait que d’un démarrage. Le Kit de démarrage REST WCF est un projet CodePlex parrainé par Microsoft qui fournit un ensemble d’extensions WCF et d’intégration de clé Visual Studio conçue spécifiquement pour simplifier les tâches de développement axées sur REST. La plupart des fonctionnalités trouvées dans le kit de démarrage REST WCF aujourd’hui trouveront probablement leur chemin dans les futures versions du .NET Framework, mais il n’y a aucune raison d’attendre, vous pouvez commencer à mettre ces fonctionnalités à utiliser aujourd’hui.

À propos de theAuthor

Aaron Skonnard est un fondateur de Pluralsight, un fournisseur de formation Microsoft offrant des cours de développeur à la demande dirigés par des instructeurs et à la demande. Ces jours, Aaron passe la plupart de son temps à enregistrer Pluralsight On-Demand ! Cours axés sur cloud computing, Windows Azure, WCF et REST. Aaron a passé des années à écrire, parler et enseigner des développeurs professionnels dans le monde entier. Vous pouvez l’atteindre à http://pluralsight.com/aaron et http://twitter.com/skonnard.

Ressources additionnelles