¡Hello World Katana!

Buenas! En este post vamos a ver como empezar a trabajar con Katana. En un post anterior hablé un poco de Katana y mis fantasías (más o menos húmedas) de lo que podría ser un un futuro.

Antes que nada hagamos un repaso rápido:

  1. OWIN: Open Web Interface for .NET. Especificación que define un estándard para comunicar servidores web y aplicaciones web (en tecnología .NET).
  2. Katana: Implementación de Microsoft de la especificación OWIN.

¿Cuál es la ventaja principal de hacer que una aplicación web sea compatible con OWIN? Pues simplemente que desacoplas esta aplicación web del servidor usado. Cualquier servidor (que sea compatible con OWIN) podrá hospedar tu aplicación. Esto abre la puerta a tener aplicaciones web self-hosted.

Empezando con Owin y Visual Studio 2012

En este primer post vamos a realizar la aplicación más posible sencilla (un hello world, originalidad a tope).

Para empezar abre VS2012 (o VS2013 si lo tienes, para este post da igual) y crea una aplicación de consola. Luego añade con NuGet los siguientes paquetes:

  1. Microsoft.Owin.Hosting
  2. Microsoft.Owin.Host.HttpListener
  3. Microsoft.Owin.Diagnostics
  4. Owin.Extensions

Actualmente están en prerelase así que incluye el flag –IncludePreRelase  cuando lances el comando Install-Package desde la consola de NuGet.

Una vez tengos estos paquetes instalados, ya podemos desarrollar nuestra aplicación. Lo primero que necesitamos es una clase que ponga en marcha nuestra aplicación:

class Program

{

    public static void Main(string[] args)

    {

        var uri = "http://localhost:8080/";

 

        using (WebApp.Start<Startup>(uri))

        {

            Console.WriteLine("Started");

            Console.ReadKey();

            Console.WriteLine("Stopping");

        }

    }

}

Usamos la clase WebApp del paquete Microsoft.Owin.Hosting para poner en marcha nuestra aplicación. El parámetro genérico (Startup) es el nombre de una clase que será la que configurará nuestra aplicación.

Veamos el código:

public class Startup

{

    public void Configuration(IAppBuilder app)

    {

        app.UseHandlerAsync((req, res) =>

        {

            res.ContentType = "text/plain";

            return res.WriteAsync("Hello Katana. You reached " + req.Uri);

        });

 

    }

}

El método Configuration se invoca automáticamente y se recibe un parámetro IAppBuilder. Dicha interfaz estaba definida en la specificación de OWIN (http://owin.org/spec/owin-0.12.0.html#_2.10._IAppBuilder) pero desapareció en la versión final.

Katana usa esta interfaz para permitir a la aplicación web configurar el pipeline de procesamiento de peticiones. De momento nuestra aplicación es muy simple: Por cada petición, construirá una respuesta con el texto “Hello Katana. You reached “ seguido de la URL navegada.

Si ejecutamos el proyecto, y abrimos un navegador y nos vamos a localhost:8080, vemos que nuestra aplicación ya está en marcha:

image

Fíjate que nuestra aplicación es un ejecutable. No hay servidor web, ni cassini, ni IIS Express, ni IIS, ni nada 🙂

Agregando un módulo

OWIN se define de forma totalmente modular. Por un lado tenemos un Host (en este caso es nuestro ejecutable a través del objeto WebApp del paquete Microsoft.Owin.Host), varios módulos y finalmente la aplicación en si.

Los módulos implementan un “delegado” que se conoce como AppFunc (aunque no hay ningún delegado real con este nombre). AppFunc es realmente Func<IDictionary<string, object>, Task>, es decir recibir un IDictionary<string, object> y devolver un Task.

La idea es que un módulo recibe un diccionario (claves cadenas, valores objects) que es el entorno del servidor y devuelve una Task que es el código que este módulo debe ejecutar.

Los módulos están encadenados y cada módulo debe llamar al siguiente. El aspecto genérico de un módulo queda así:

using AppFunc = Func<IDictionary<string, object>, Task>;

public class OwinConsoleLog

{

    private readonly AppFunc _next;

    public OwinConsoleLog(AppFunc next)

    {

        _next = next;

    }

    public Task Invoke(IDictionary<string, object> environment)

    {

        Console.WriteLine("Path requested: {0}", environment["owin.RequestPath"]);

        return _next(environment);

    }

}

El módulo define el método Invoke y recibe como parámetro el diccionario que contiene el entorno del servidor. Luego llama al siguiente módulo y le pasa el entorno. Fíjate que la clase OwinConsoleLog no implementa ninguna interfaz ni nada, pero debe tener un método llamado Invoke que sea conforme al “delegado” AppFunc (es decir que devuelva un Task y reciba un diccionario).

Para añadir el módulo simplemente llamamos al método Use de IAppBuilder pasándole el Type de nuestro módulo:

public void Configuration(IAppBuilder app)

{

    app.Use(typeof (OwinConsoleLog));

    app.UseHandlerAsync((req, res) =>

    {

        res.ContentType = "text/plain";

        return res.WriteAsync("Hello Katana. You reached " + req.Uri);

    });

}

Si ahora ejecutas el proyecto y navegas a localhost:8080 verás como se imprimen las distintas peticiones recibidas:

image

¡Listos! Hemos creado nuestra primera aplicación web, compatible con OWIN y auto hospedada 🙂

En sucesivos posts iremos desgranando más cosillas sobre OWIN y Katana…

Saludos!

2 comentarios sobre “¡Hello World Katana!”

  1. Hola Eduard,

    ¿No sería lógico que hubiese un interface IModule para los módulos e IAppConfigurator o algo así para la clase Startup?

    Supongo que la idea es no tener una dependencia «fuerte» (vamos, una referencia) entre la librería que implementa los módulos y el propio API de OWIN, pero aunque no la hagas explícita, la dependencia sigue ahí, por lo que no acabo de ver muy clara la ventaja.

    Seguramente haya algo que se me escape…

    Un saludo,

    Juanma.

  2. Que poco me gusta que tenga que haber un metodo llamado configuration y otro invoke así por que sí, sin interfaces ni nada «formal», solo por convención :/. Creo que es un paso atras, pero bueno…

Responder a cpsaez Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *