Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 15 de octubre de 2013
¿Crear nuestra propia arma?Continuando la serie sobre esta simpática pareja, en esta ocasión vamos crear nuestro primer módulo middleware personalizado. Utilizando la infraestructura creada por Katana, veremos lo fácil que resulta crear un componente de este tipo e introducirlo en el pipeline para que procese peticiones.

Pero antes, como siempre, permitidme que recuerde los otros posts de la serie a los que acabáis de llegar:
Venga, vamos a remangarnos… ;-)

1. Middlewares: vamos a llevarnos bien

Como sabemos, cuando una petición llega a una aplicación OWIN, es procesada por los middlewares introducidos en el pipeline durante la inicialización mediante lo que podríamos denominar una cadena colaborativa de ejecución o proceso de peticiones.

La petición es normalmente procesada por el primer módulo introducido en el pipeline; éste realizaría su trabajo y, si procede, cedería el proceso al siguiente módulo, donde volvería a ocurrir exactamente lo mismo, y así hasta llegar al último módulo del pipeline, o bien a un módulo que decida no delegar la petición al siguiente de la lista.

Y en la respuesta ocurriría lo mismo. El último middleware ejecutado del pipeline retornaría el control a quien lo inició, y éste tendría posibilidad de alterar el resultado, proceso que iría repitiéndose hasta llegar de nuevo al primer módulo del pipeline, quien finalmente tendría la última palabra en cuanto a la respuesta retornada al servidor.

Proceso de una petición OWIN
En el diagrama anterior se puede observar un pipeline con cuatro módulos middleware, y cómo al entrar la petición van pasándosela de unos a otros hasta que el tercero decide procesarla por completo y dar por finalizada la secuencia. El cuarto módulo ni siquiera se enterará de que entró una petición.
A raíz de ahí, se puede intuir que un middleware necesita dos cosas para ejecutarse:
  • Primero, información sobre la petición, que determinará la forma en que el módulo la procesará, así como un canal para escribir la correspondiente respuesta, por si lo necesita. Tened en cuenta que no todos los módulos necesitan escribir respuesta para el cliente: imaginad un módulo de logging.
  • Segundo, información sobre el siguiente módulo, para que pueda invocarlo cuando interese y continuar así la cadena de ejecución.
Básicamente, estos dos elementos ya están dan pistas sobre cómo debemos implementar los módulos, que es lo que haremos a continuación. Y algo más adelante, veremos cómo Katana nos facilitará bastante esta tarea al proporcionar infraestructura para hacerlo de forma aún más sencilla.

2. ¡Mira mamá, me he hecho un middleware!

Aunque sólo sea para lograr el orgullo de una madre, vamos a construir e insertar en el pipeline nuestro primer módulo middleware. Y como no hay que ser más ambicioso de la cuenta en estas primeras fases, crearemos uno sencillito cuyo objetivo es simplemente saludar al usuario cuando se realice una petición a una URL determinada; por ejemplo, si accedemos a /hello/name=john, mostrará como resultado “Hello, john”.

Sobrecargas del extensor IAppBuilder.UseUna de las sobrecargas del extensor IAppBuilder.Use() permite indicar un delegado o lambda en el que se puede implementar directamente el middleware. Como parámetros, recibirá el contexto de la petición (IOwinContext), y un delegado mediante el cual podemos ejecutar el siguiente módulo en el pipeline; y de retorno, siempre un objeto Task, para permitir el proceso asíncrono de la petición.

Por tanto, podríamos crear e insertar nuestro primer módulo en el pipeline de la siguiente forma:
public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.Use((context, nextModule) =>
            {
                if (context.Request.Path.StartsWith("/hello") &&
                    !string.IsNullOrWhiteSpace(context.Request.Query.Get("name")))
                {
                    return context.Response.WriteAsync("Hello, " + 
                                   context.Request.Query.Get("name")
                    );
                }
                return nextModule();
            });

        
        // Other modules
    }
}
Observad que si se cumple la condición de entrada se trata de un módulo final, como el tercero del diagrama anterior: se procesará la petición y no dará oportunidad de ejecutarse a otros módulos que ocupen una posición posterior en el pipeline. En caso contrario, cederá el turno al siguiente middleware.

El parámetro context que declaramos en la lambda es un objeto que implementa IOwinContext, que es simplemente un wrapper sobre el diccionario de entorno del que ya hemos hablado muchas veces. Sus propiedades y métodos proporcionan acceso al contenido de dicho diccionario de forma tipada y mucho más cómoda que haciéndolo a mano. Por ejemplo, desde ahí tendremos acceso al Request (de tipo IOwinRequest), Response (IOwinResponse), o información de autenticación del usuario, entre otros.

El parámetro que hemos llamado nextModule es un delegado que podemos utilizar para invocar de forma directa al siguiente middleware en el pipeline. Su tipo de retorno es un Task, lo que permite utilizarlo como resultado de la función para que se ejecute de forma asíncrona.

Y en ejecución quedaría algo así:

Middleware en acción

3. ¡Mira, mamá, ahora sin manos!

Como estamos ya lanzados, vamos a crear otro middleware que insertaremos en primer lugar en el pipeline, y que añadirá al resultado del resto de módulos un texto indicando el tiempo que ha tardado en procesarse la petición:
app.Use(async (context, next) =>
    {
        var watch = Stopwatch.StartNew();
        await next();
        watch.Stop();
        await context.Response.WriteAsync(
                "-- Elapsed: " + watch.Elapsed
        );
    });
... // Other modules
En este caso, utilizamos una lambda asíncrona (observad el async en su definición) para poder utilizar await cuando queremos esperar a que finalice el proceso del resto de módulos del pipeline. Podríamos haberlo hecho también sin esto, pero así mola más ;-)

Si insertamos este pequeño módulo al principio del pipeline en el epígrafe anterior, podremos tener resultados como este:

Stopwatch middleware en ejecución

4. Pero mejor tener una Katana a mano…

Obviamente, esta no es la mejor forma de crear middlewares. Katana ofrece infraestructura para hacerlo más cómoda y fácilmente a través de su abstracta OwinMiddleware, definida de la siguiente forma:
public abstract class OwinMiddleware
{
    protected OwinMiddleware Next { get; set; }
    protected OwinMiddleware(OwinMiddleware next)
    {
        this.Next = next;
    }
    public abstract Task Invoke(IOwinContext context);
}
Para crear middlewares, simplemente tenemos que heredar de OwinMiddleware, implementar su constructor, que recibe una referencia al siguiente módulo del pipeline en forma de objeto OwinMiddleware, e introducir la lógica deseada en el método Invoke().

El siguiente módulo introduce en encabezado en las respuestas cuando la petición lo atraviesa:
public class PoweredByMiddleware : OwinMiddleware
{
    private readonly string _by;

    public PoweredByMiddleware(OwinMiddleware next, string by) : base(next)
    {
        _by = by;
    }

    public override Task Invoke(IOwinContext context)
    {
        context.Response.Headers.Append("Powered-by", _by);
        return Next.Invoke(context);
    }
}
Y en este caso, podríamos insertar el módulo al principio del middleware usando la sobrecargas genérica de Use(), y pasándole como argumento los valores que deseamos enviar al constructor del componente:
public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.Use<PoweredByMiddleware>("Owin");
        ... // Other modules
    }
}
En tiempo de ejecución quedaría algo así:

PoweredByMiddleware en ejecución

Bueno, pues creo que con lo visto hasta el momento ya tenemos una idea general de qué es y cómo funciona Katana y Owin, al menos a alto nivel. En siguiente artículo continuaremos avanzando en nuestro camino hacia el cinturón negro, y hablaremos de otras variantes que podemos utilizar para añadir módulos al pipeline: los extensores Map() y Run().

Publicado en Variable not found.

2 Comentarios:

Jose dijo...

Hola Jose Manuel

Como siempre tus explicaciones son geniales.

He intentado reproducir el ejemplo que vas comentando en el código, pero nunca consigo que pase por el método Configuration de la clase Startup.

He seguido los pasos del post anterior y poniendo un punto de interrupción no consigo que pare dentro de la función Configuration.

He probado con un proyecto aplicacion web o website, pero en ninguna de las ocasiones consigo que funcione.

¿Por que puede ser?

Muchas gracias

José María Aguilar dijo...

Hola!

Pues tiene pinta de deberse a que te falta por instalar el paquete Nuget que "engancha" OWIN con tu aplicación web.

Mira este post: http://www.variablenotfound.com/2013/10/owin-y-katana-iv-startup-y-configuration.html

Un saludo!