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, 12 de noviembre de 2013
Katana de Hatori HanzoEn todos los posts anteriores hemos visto cómo usar middlewares asumiendo que su ejecución era puramente secuencial y en el mismo orden en el que habían sido añadidos al pipeline.

Sin embargo, cuando sabemos que nuestra aplicación va a correr sobre un host ASP.NET/IIS, utilizando el adaptador disponible en el paquete Microsoft.Owin.Host.SystemWeb, podemos insertarlos en puntos determinados del pipeline de ejecución de IIS (la famosa “canalización integrada”) para conseguir una mejor integración con frameworks y aplicaciones existentes que a priori no entienden de OWIN (p.e., una aplicación MVC 4 o WebForms).


En este post vamos a ver cómo funciona este mecanismo pero, primero, permitid que recuerde a los que acabáis de llegar los posts anteriores de la serie:

En el principio era el módulo HTTP

Un middleware OWIN no difiere mucho, al menos en concepto, de lo que tradicionalmente hemos llamado módulos (Http modules) en ASP.NET. En ambos casos se trata de componentes que podemos insertar en la cadena de acciones que se ejecutan cuando se procesa una petición para introducir lógica personalizada.

La diferencia es que los primeros actúan sobre el pipeline de OWIN y los segundos sobre el pipeline de IIS. En este último caso, la lógica de proceso de los módulos se implementa en handlers de los eventos que representan los principales estados por los que pasa una petición: AcquireRequestState, AuthenticateRequest, AuthorizeRequest, etc. Normalmente para crear un módulo lo que hacíamos era suscribirnos a los eventos en los que queríamos tomar el control:
public class MyModule: IHttpModule
{
   public void Init(HttpApplication context)
   {
      context.BeginRequest += context_BeginRequest;
      context.AuthorizeRequest += context_AuthorizeRequest;
      context.AuthenticateRequest += context_AuthenticateRequest;
      ...
   }
   ...
}
Estos eventos forman parte del pipeline de ejecución de IIS, siguen un orden predefinido y, por supuesto, están presentes en la ejecución de todas las aplicaciones que corren sobre este servidor. Por esta razón, el adaptador OWIN para IIS/ASP.NET permite entrar en ese juego e insertar los middlewares atendiendo también al orden en que se producen dichos eventos.

La orden de la Katana

La inclusión del paquete Microsoft.Owin.Host.SystemWeb hace que tengamos disponible en el espacio de nombres Owin el extensor UseStageMarker(),  que permite indicar en qué fase de ejecución del pipeline de IIS queremos introducir los módulos middleware que hemos indicado previamente. Es decir, son marcadores que ponemos en el código para indicar algo como “todos los middlewares introducidos hasta aquí van a ejecutarse en el estado X del pipeline de IIS”.

En siguiente ejemplo añadiríamos los middlewares Foo y Bar a la fase de autenticación del proceso, y Baz a la de autorización:
app.Use<Foo>();
app.Use<Bar>();
app.UseStageMarker(PipelineStage.Authenticate);

app.Use<Baz>();
app.UseStageMarker(PipelineStage.Authenticate);
Como se puede intuir, UseStageMarker() recibe como argumento un valor de la enumeración PipelineStage que refleja la fase del proceso de la petición de ASP.NET a las que queremos enganchar los módulos middleware. Las opciones disponibles, en orden de ejecución, son:
public enum PipelineStage
{
   Authenticate,
   PostAuthenticate,
   Authorize,
   PostAuthorize,
   ResolveCache,
   PostResolveCache,
   MapHandler,
   PostMapHandler,
   AcquireState,
   PostAcquireState,
   PreHandlerExecute,
}
Por defecto todos los middlewares se añaden a la fase PreHandlerExecute, es decir, al final del proceso y justo antes de ejecutarse el handler que procesa la petición.

Un detalle importante es que el orden en que llamamos a UseStageMarker() se tiene en cuenta. Si lo usamos varias veces en un código, tiene que ser en el mismo orden en el que ocurren los eventos a los que estamos añadiendo los middlewares. Es decir, no podríamos llamar a este método usando Authorize y cuatro líneas más abajo llamarlo para la fase Authenticate, que es previa a la primera; en este caso, se vincularían todos los módulos afectados a la fase más temprana de las usadas, que en este caso es Authenticate.

¿Un ejemplo?

Pues sí, mejor vamos a ilustrar todo esto con un ejemplo. Crearemos un módulo HTTP que enviará un mensaje al cliente en la fase MapHandler y veremos cómo podemos configurar nuestros módulos OWIN para que se ejecuten antes y después de éste.

El módulo HTTP es el siguiente:
public class MyModule: IHttpModule
{
   public void Init(HttpApplication context)
   {
      context.MapRequestHandler += context_MapRequestHandler;
   }
   public void Dispose()
   {
   }

   void context_MapRequestHandler(object sender, EventArgs e)
   {
      HttpContext.Current.Response.Write(
       "<p>Hi from the module (MapRequestHandler event)</p>");
   }
}
Y lo insertamos en el pipeline desde el web.config:
<system.webServer>
   <modules>
      <add name="MyModule" type="DemoApp.MyModule"/>
   </modules>
</system.webServer>
Por otra parte, tenemos el siguiente módulo middleware:
public class MyMiddleware : OwinMiddleware
{
   private readonly string _text;

   public MyMiddleware(OwinMiddleware next, string text)
          : base(next)
   {
      _text = text;
   }

   public async override Task Invoke(IOwinContext context)
   {
      context.Response.Write("<p>"+ _text + " begin</p>");
      await Next.Invoke(context);
      context.Response.Write("<p>"+ _text + " end</p>");
   }
}
Y añadimos varias instancias al pipeline OWIN en la clase Startup, así como un módulo final que envía un mensaje al cliente:
public class Startup
{
   public void Configuration(IAppBuilder app)
   {
      app.Use<MyMiddleware>("One");
      app.Use<MyMiddleware>("Two");
      app.Use<MyMiddleware>("Three");
      app.Use<MyMiddleware>("Four");

      app.Run(context =>
         context.Response.WriteAsync("<p><strong>Hello world!!</strong></p>")
      );
   }
}
Si ejecutamos esta aplicación, obtendremos en el navegador lo siguiente:

Ejecución
Observad que primero se ha ejecutado el módulo HTTP porque los middlewares están asociados por defecto a una fase posterior del proceso del pipeline. A partir de ahí, se van ejecutando sucesivamente en sentido ascendente hasta llegar al módulo final, que retorna el valor y continúa la ejecución de los módulos, ahora en orden inverso.

Utilizamos ahora UseStageMarker() para modificar el momento de ejecución de los módulos y vemos seguidamente el resultado:
app.Use<MyMiddleware>("One");
app.Use<MyMiddleware>("Two");
app.UseStageMarker(PipelineStage.Authorize);
app.Use<MyMiddleware>("Three");
app.Use<MyMiddleware>("Four");
app.UseStageMarker(PipelineStage.PostMapHandler);

image
Como se observa, a primera llamada a UseStageMarker() ha desplazado los dos primeros middlewares a un estado previo al capturado desde el módulo HTTP, y los dos siguientes a uno posterior, por lo que el orden de ejecución ha variado.

En definitiva, lo interesante de todo esto es que podemos utilizar componentes middleware en aplicaciones ASP.NET, aunque éstas estén muy alineadas con los eventos impuestos por el pipeline de IIS. Y esto hace virtualmente posible utilizar los módulos OWIN en cualquier tipo de aplicación, siempre que se cumplan los requisitos mínimos impuestos por Katana y los middlewares que utilicemos.

Publicado en Variable not found.

2 Comentarios:

Reynier dijo...

Hola Jm

Gracias por tu aporte a nuestro conocimiento.
Ya se que esta libertad de owin y katana nos va a permitir maravillas pero hoy dia esta refinado lo suficiente para ser usado en produccion. Me preocupa el performance. Si puedes comentarme al respecto seria grandioso.

Saludos

josé M. Aguilar dijo...

Hola, Reynier.

En principio se trata de un producto estable y listo para producción, aunque obviamente no tenga la misma madurez que otros que llevan ya varias versiones a sus espaldas. Fíjate que las nuevas versiones de WebAPI o SignalR ya están totalmente apoyados en Katana, y esto demuestra la apuesta de Microsoft por estas tecnologías.

En cuanto al rendimiento, en pura teoría debería ser superior a la alternativa ASP.NET tradicional, aunque aún tendremos que esperar un poco para poder quitarnos esta pesada mochila de encima (mira este post: http://weblog.west-wind.com/posts/2013/Nov/23/Checking-out-the-Helios-IIS-Owin-Web-Server-Host).

Saludos.