Nouvelles Chroniques d'Amethyste

Penser au Sens, pas au Verbe

Autour des middlewares Owin

Poster un commentaire

Ce n’est pas la première fois que j’aborde Owin, c’est quelque chose d’important dès maintenant et indispensable avec le futur ASP.NET 5 où tout sera Owin. Vous pourrez trouver une série d’articles détaillés ici:

https://amethyste16.wordpress.com/category/owin-2/

J’aimerai dans celui-ci faire un petit rappel des points fondamentaux. Le minimum à connaître.

Pour rappel, avec Owin on vient comme on est. Owin est juste un moteur de pipeline qui va lancer les middlewares qui y seront déclarés, dans l’ordre où il les rencontre.

Si vous avez besoin de cache, d’hébergement dans IIS et d’authentification via un compte Twitter, cela fait 3 middlewares et donc 3 déclarations à faire dans le bon ordre. Le pipeline est vide par défaut.

Vous voyez la simplification par rapport au pipeline ASP.NET habituel et l’analogie avec d’autres outils tels NodeJS.

Techniquement, un middleware Owin est un délégué avec cette signature:


Func<IDictionary<string, object>, Task>

Il s’agit d’une méthode (Func) qui retourne une Task et attend un dictionnaire d’objets. Ce dictionnaire est appelé environnement Owin. Task signifie que la méthode conseillée est de travailler en asynchrone, même si techniquement on pourrait se débrouiller sans.

Ca peut sembler un peu compliqué, mais Owin est une abstraction de bas niveau qui doit s’adapter à n’importe quel contexte. Votre application va être hébergée par on ne sait qui, va tourner peut-être sous Windows, mais pas forcément. La seule hypothèse que l’on fait est que l’on est dans .Net. Le dictionnaire a paru être une abstraction suffisamment générique à Microsoft.

Si vous avez envie de lire la spec, c’est ici:

http://owin.org/

 

Note: Vous avez peut-être entendu parler du projet Katana. Katana est une implémentation particulière de la spécification Owin proposée par Microsoft. Elle n’est pas la seule.

Je ne vais pas m’en servir pour toute la suite car je préfère montrer comment écrire des middlewares les plus génériques possibles, c’est à dire aptent à s’intégrer nativement dans n’importe quelle implémentation et pas juste Katana.

Cela ne signifie pas qu’il soit incorrect de se servir de Katana ou un autre outil concurrent, juste qu’il s’agit d’un article de niveau tutoriel. Katana n’apporte rien d’utile à ce stade.

J’ai mis en bibliographie des tutos Katana.

 

Voyons comment on utilise tout ça dans une application concrète.

 

On commence par créer une application Web vide qui devrait donc ressembler à ceci:

2015-11-11_00-43-13

On complète en ajoutant le Nuget Microsoft.Owin.Host.SystemWeb. Ce package ajoute au projet Owin bien sûr, mais aussi la plomberie qui permet de lancer le pipeline sur IIS.

Le fichier packages.config qui garde la trace des packages Nuget installés dans l’application est similaire à celui-ci:

2015-11-11_00-46-38

Il ne reste plus qu’à ajouter une classe dotée des caractéristiques qui suivent:

  • Elle s’appelle Startup
  • Elle expose méthode appelée Configure et qui attend un IAppBuilder:

 

public class Startup
{
   public void Configure(IAppBuilder app)
   {
 
   }
}

Il s’agit là des noms par défaut, mais grâce à des attributs ou de la configuration on pourrait tout nommer comme on le souhaite.

Configure est la méthode qui justement configure le pipeline Owin. Pour l’instant il est vide.

Cela peut sembler étonnant puisque l’on héberge le code dans IIS. On s’attendrait donc à rencontrer un middleware IIS ou Asp.Net.

Pour l’instant ASP.NET n’est pas Owin, ça va venir en ASP.NET 5 seulement. Dans notre exemple la partie hébergement ne sera donc pas assurée par un middleware, mais la plomberie habituelle. L’ancien modèle et Owin cohabitent sans problèmes.

 

Ajoutons un middleware. L’opération s’effectue avec la méthode app.Use().


public void Configuration(IAppBuilder app)
{
   app.Use(async (ctx, next) =>
   {
      await ctx.Response.WriteAsync("Hello");
   });
}

Examinons un peu ce code.

On remarque tout de suite que le délégué est appelé de façon asynchrone.

Le paramètre ctx est une instance de IOwinContext qui est une classe très légère par dessus le l’environnement. Elle se contente d’exposer dans des propriétés les éléments les plus notables de l’environnement Owin.

La propriété next est un autre middleware, celui qui vient ensuite dans le pipeline.

C’est sans surprise que l’exécution affiche juste Hello.

Modifions le code ainsi:

public void Configuration(IAppBuilder app)
{
   app.Use(async (ctx, next) =>
   {
      Debug.WriteLine("Premier passage");
      await next();
      Debug.WriteLine("Deuxième passage");
   });
 
   app.Use(async (ctx, next) =>
   {
      Debug.WriteLine("Envoyer Hello");
      await ctx.Response.WriteAsync("Hello");
   });
}

On note la présence d’un deuxième middleware ainsi que des Debug pour effectuer des traces. Côté exécution on va surtout s’intéresser à la fenêtre output dans Visual Studio:

2015-11-11_02-10-44

On voit apparaître un point fondamental du fonctionnement du pipeline Owin.

Tout d’abord les middleware sont exécutés dans l’ordre où on les enregistre, d’où l’affichage de ‘Premier passage’.

 

On voit également l’usage de next.

On l’a dit, il s’agit d’un délégué. On l’utilise donc comme une méthode. Son rôle est de lancer de façon asynchrone le délégué suivant, c’est à dire celui qui affiche ‘Hello’.

L’exécution est asynchrone, le deuxième Debug est donc une continuation ce qui explique qu’il ne s’exécute qu’une fois terminé l’exécution du deuxième délégué.

Un expérience intéressante est de réécrire le premier middleware ainsi, sans l’appel à next:


app.Use((ctx, next) =>
{
   Debug.WriteLine("Premier passage");
   Debug.WriteLine("Deuxième passage");
});

Cette fois le navigateur n’affiche plus rien.

Le fonctionnement des middlewares rappelle un peu les IHttpModule et les IHttpHandler d’ASP.NET. Mais sans la plomberie autour, en particulier les événements.

 

Nous pouvons tout à fait utiliser le dictionnaire dans un middleware pour transmettre des informations. Essayons par exemple d’écrire un middleware qui mesure le temps de traitement d’une requête:

app.Use(async (ctx, next) =>
{
   Stopwatch stopWatch = new Stopwatch();
   stopWatch.Start();
   ctx.Environment["stopWatch"] = stopWatch;
 
   await next();
 
   stopWatch = (Stopwatch)ctx.Environment["stopWatch"];
   Debug.WriteLine("Durée (ms): " + stopWatch.ElapsedMilliseconds);
});

On obtient alors:

2015-11-11_10-53-29

L’environnement est commun à tous les middlewares.

 

Mettre du code comme ça dans le pipeline n’est pas très élégant et peut devenir problématique si l’application se complexifie. Une pratique courante et conseillée est de créer une classe de type middleware, c’est à dire:

  • un constructeur qui attend un middleware
  • une méthode appelée Invoke qui attend un environnement et retourne une Task
public class StopwatchMiddleware
{
   private AppFunc _next;
 
   public StopwatchMiddleware(AppFunc next)
   {
      _next = next;
   }
 
   public Task Invoke(IDictionary<string,object> environment)
   {
 
   }
}

AppFunc est un middleware Owin tel qu’il a été définit plus haut. Pour alléger l’écriture on ajoutera donc cet alias:


using AppFunc = System.Func<System.Collections.Generic.IDictionary<string, object>, System.Threading.Tasks.Task>;

Il ne reste plus qu’à remplir Invoke ce que l’on peut faire ainsi:

public async Task Invoke(IDictionary<string,object> environment)
{
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
environment["stopWatch"] = stopWatch;
 
await _next(environment);
 
stopWatch = (Stopwatch)environment["stopWatch"];
Debug.WriteLine("Durée (ms): " + stopWatch.ElapsedMilliseconds);
}

Notez la présence de async afin de pouvoir lancer le middleware suivant de façon asynchrone avec await. Le reste du code est pour l’essentiel identique.

Côté pipeline on aura:


app.Use<StopwatchMiddleware>();

Je vous laisse le soin de constater que ça fonctionne comme avant.

 

Sans que cela soit obligatoire, on peut améliorer légèrement le code de la façon qui suit.

On commence par écrire une méthode d’extension que j’aime bien placer dans l’espace de noms Owin:


namespace Owin
{
   public static class StopwatchMiddlewareExtension
   {
      public static void UseStopwatch(this IAppBuilder app)
      {
         app.Use<StopwatchMiddleware>();
      }
   }
}

On peut alors écrire ceci dans le pipeline:


app.UseStopwatch();

Et c’est d’ailleurs souvent ainsi que les middlewares sont fournis, mais le code d’avant est parfaitement correct.

Bibliographie

Tuto Katana:

http://www.asp.net/aspnet/overview/owin-and-katana/an-overview-of-project-katana

http://stackoverflow.com/questions/21308585/when-should-i-use-owin-katana

 

 

 

 

 

 

 

 

 

Publicités

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s