Nouvelles Chroniques d'Amethyste

Penser au Sens, pas au Verbe

Chaîner les middlewares Katana/Owin

Poster un commentaire

Le code que l’on a vu jusqu’à présent nous a permis de présenter pas mal de concepts Owin, sauf un pourtant essentiel: le pipeline! Nous savons juste que c’est le rôle de Startup.Configuration de le construire.

Comment injecte t’on des middleware? Comment les enchaîne t’on?

Et tant qu’à faire, autant être exigeant et souhaiter exécuter de façon conditionnelle certaines parties du pipeline.

 

Nous allons explorer ces différentes possibilités dans cet article.

Chaînage des composants

Les deux méthodes de base exposées par IAppBuilder sont Run() et Use(). Elles jouent un rôle différent.

Regardez ce code:

public void Configuration(IAppBuilder app)
{
    app.Run(context =>
    {
        context.Response.ContentType = "text/html";
        return context.Response.WriteAsync("<h1>Premier composant</h1>");
    });

    app.Run(context =>
    {
        context.Response.ContentType = "text/html";
        return context.Response.WriteAsync("<h1>Deuxième composant</h1>");
    });
}

L’exécution donne ceci:

12-04-2014 18-40-41

Seul le premier composant de la pile est exécuté. C’est le comportement normal de Run(). Run() ne chaîne pas les composants qui le suit et ne fournis aucun moyen de le faire. Ils seront donc ignorés.

Si on a besoin de chaîner on doit utiliser Use().

public void Configuration(IAppBuilder app)
{
    app.Use((IOwinContext context,Func<Task> next) =>
    {
        context.Response.ContentType = "text/html";
        context.Response.WriteAsync("<h1>Premier composant</h1>");

        return next.Invoke();
    });

    app.Run(context =>
    {
        context.Response.ContentType = "text/html";
        return context.Response.WriteAsync("<h1>Deuxième composant</h1>");
    });
}

Et cette fois on observe:

12-04-2014 18-49-13

Use accepte de nombreuses surcharges, mais celle qui nous occupe attend le paramètre next. Ce paramètre expose la méthode Invoke() responsable de l’appel des composants suivants. Notez bien que cet appel n’est pas automatique, il appartient à votre middleware de le faire ou non. Nous verrons plus loin quel partie on peut tirer de cela.

 

Exploitation de la pile bidirectionnelle

Dans cet exemple nous nous sommes déplacés dans une direction (premier composant, vers deuxième composant).

En fait le pipeline Owin est également parcourut en sens inverse et nous pouvons évidemment écrire du code  correspondant!

C’est là que nous allons comprendre le rôle des Task qui n’ont guère été utilisées jusqu’ici bien que présentes dans un peu toutes les signatures.

Un middleware est supposé renvoyer des Task, cela signifie que l’on peut exécuter du code à sa suite avec ContinueWith, ou mieux encore await si on est en .Net 4.5. C’est ici que nous allons placer notre code de retour.

Regardons comment:

public void Configuration(IAppBuilder app)
{
   app.Use(async (IOwinContext context,Func<Task> next) =>
   {
      // exécution du premier composant
      context.Response.ContentType = "text/html";
      await context.Response.WriteAsync("<h1>Premier composant</h1>");

      // exécution des composants qui suivent
      await next.Invoke();

      // on revient du deuxième composant
      await context.Response.WriteAsync("<h1>Premier composant: Retour du deuxième composant</h1>");
   });

    app.Run(context =>
    {
        context.Response.ContentType = "text/html";
        return context.Response.WriteAsync("<h1>Deuxieme composant</h1>");
     });
}

 

L’affichage est le suivant:

Premier composant

Deuxieme composant

Premier composant: Retour du deuxieme composant

 

Graphiquement on peut représenter les choses ainsi:

12-04-2014 19-20-34

Ce double parcours du pipeline est important et pas toujours bien compris d’après certaines questions posées dans les forums. Pour compléter l’exemple qui précède, je reprends un exemple plus sophistiqué trouvé sur ce blob:

http://blog.tomasjansson.com/owin-middleware/

Mais je modernise le code avec async/await.

Le parcours dans le pipeline est le suivant:

13-04-2014 12-46-06

Note: a titre d’exercice il peut être intéressant de faire le code soi même.


public void Configuration(IAppBuilder app)
{
    // Middleware 1
    app.Use(async (context, next) =>
    {
        context.Response.ContentType = "text/html";
        await context.Response.WriteAsync("Middleware1 aller<br/>");
        await next.Invoke();
        await context.Response.WriteAsync("Middleware1 retour<br/>");
     });

    // Middleware 2
    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("Middleware2 aller<br/>");
        await next.Invoke();
        await context.Response.WriteAsync("Middleware2 retour<br/>");
    });

    // application
    app.Run(context =>
    {
        return context.Response.WriteAsync("Application<br/>");
    });
}

Et on obtient l’affichage:

Middleware1 aller
Middleware2 aller
Application
Middleware2 retour
Middleware1 retour

 

Maintenant les choses devraient être claires.

 

Exécution conditionnelle du pipeline

Les pipelines écrits jusqu’à présent sont indépendant du chemin dans l’url. La méthode Map nous permet justement de lier un composant à un chemin particulier.

public void Configuration(IAppBuilder app)
{
    app.Map("/auteurs", app2=>
    {
        app2.Use(async (context, next) =>
        {
            context.Response.ContentType = "text/html";
            await context.Response.WriteAsync("<h1>Hugo</h1>");
            await context.Response.WriteAsync("<h2>Zola</h2>");

            await next.Invoke();
        });
    });

    app.Run(async context =>
    {
        await context.Response.WriteAsync("<h1>Quel auteur cherchez vous?</h1>");
    });
}

 

Observez le comportement avec les url:

localhost:18000

Quel auteur cherchez vous?

et localhost:18000/auteurs

Hugo

Zola

 

Observez bien la syntaxe de Map(). La fonction est juste un wrapper qui permet d’associer un pipeline à un chemin particulier.

Il est tout à fait possible d’imbriquer les Map afin de donner une sémantique à une url telle localhost:18000/auteurs/hugo.

public void Configuration(IAppBuilder app)
{
    app.Map("/auteurs", app1 =>
    {
        //auteurs/hugo
        app1.Map("/hugo", appHugo =>
        {
            appHugo.Use(async (context, next) =>
            {
                context.Response.ContentType = "text/html";
                await context.Response.WriteAsync("<h1>Les Miserables</h1>");
                await context.Response.WriteAsync("<h2>Notre Dame de Paris</h2>");
            });
         });

        //auteurs/zola
        app1.Map("/zola", appZola =>
        {
             appZola.Use(async (context, next) =>
             {
                 context.Response.ContentType = "text/html";
                 await context.Response.WriteAsync("<h1>L'Argent</h1>");
                 await context.Response.WriteAsync("<h2>Germinal</h2>");
             });
        });

        /auteurs
        app1.Use(async (context, next) =>
        {
            context.Response.ContentType = "text/html";
            await context.Response.WriteAsync("<h1>Hugo</h1>");
            await context.Response.WriteAsync("<h2>Zola</h2>");
        });
    });

    app.Run(async context =>
    {
        await context.Response.WriteAsync("Quel auteur cherchez vous?");
    });
}

 

Notez plusieurs points.

Le Map le plus générique doit être en bas de la liste, sinon les suivants ne seront jamais exécutés. Chaque déclaration Map définit un nouvel IAppBuiler. C’est lui qui devra être exécuté.

localhost:18000/auteurs donne la même chose:

Hugo

Zola

 

Mais localhost:18000/auteurs/hugo donne:

Les Misérables

Notre Dame de Paris

 

Graphiquement notre pipeline ressemble à celui-ci:

 

13-04-2014 15-39-12

Map admet une variante appelée MapWhen qui joue le même rôle que le else dans une expression if/else.

Regardons cet exemple:


app.MapWhen(context =>
{
    return DateTime.Now > new DateTime(2014,04,12);
},
trueapp =>
{
    trueapp.Run(context =>
    {
        return context.Response.WriteAsync("Le site est fermé, désolé");
    });
});

Le premier paramètre de MapWhen est un Predicate, c’est à dire un délégué qui retourne un booléen. Dans notre cas la situation examinée est si la date en cours est postérieure au 12/04/2014.

Si c’est le cas le délégué du deuxième paramètre est lancé. Il s’agit d’une méthode Run qui lance un message indiquant que le site est fermé.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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