Sirviendo ASP.NET vNext con Nowin

Nota: Este post está realizado sobre la CTP de VS2014 y la alfa de ASP.NET vNext. Todo lo dicho puede cambiar cuando salgan las versiones RTM…

¿Conoces Nowin? Es un servidor web OWIN, es decir si construimos nuestra aplicación web basándonos en middleware OWIN (p. ej. WebApi 2 o NancyFx) podemos usar este servidor web para servirla a los clientes. Ya he hablado de OWIN varias veces antes, ya que OWIN ha sido la penúltima revolución en desarrollo de aplicaciones web en tecnología Microsot (la última es vNext, claro). Microsoft hizo un esfuerzo implementando middleware OWIN y permitiendo su integración con ASP.NET (el proyecto Katana), pero ahora sale ASP.NET vNext y es lógico preguntarse… ¿en qué queda todo?

Básicamente la pregunta es si ASP.NET vNext es compatible con OWIN y si lo es, en que terminos: ¿podemos añadir componentes OWIN al pipeline de vNext y podemos usar componentes vNext como si fuesen componentes OWIN?

La respuesta rápida a las tres preguntas es sí. ASP.NET vNext es compatible con OWIN y podemos tanto añadir componentes OWIN a nuestro pipeline de ASP.NET vNext como usar un componente vNext como si fuese middleware OWIN. De hecho cualquier otra respuesta sería totalmente incomprensible.

La idea de modularización de la aplicación en componentes que tiene ASP.NET vNext está sacada directamente de OWIN (en Katana se usa IAppBuilder lo que generó un interesantísimo debate en la comunidad).

Bueno, vayamos al tajo… intentemos ver como podemos servir una aplicación ASP.NET vNext (MVC6) pero usando un servidor OWIN como Nowin para ello. Lo primero es crear una “ASP.NET vNext Console Application” usando VS2014 CTP… Eso nos permitirá ver como hospedar ASP.NET vNext en una aplicación de consola 😉

El siguiente paso será instalar MVC6 en nuestra aplicacion. No usaremos NuGet para ello, en su lugar editaremos el fichero project.json que trae VS2104 y añadiremos las siguientes líneas en la sección dependencies:

  1. "Microsoft.AspNet.Mvc": "0.1-alpha-build-*"

Por suerte VS2014 nos ofrece intellisense al editar project.json (aunque parecen no salir todas las referencias de paquetes NuGet):

image

Ahora añade un controlador de MVC a tu aplicación (HomeController) con una acción Index que simplemente retorne la vista asociada:

  1. using Microsoft.AspNet.Mvc;
  2.  
  3. namespace NowinDemo.Controllers
  4. {
  5.     public class HomeController : Controller
  6.     {
  7.         public IActionResult Index()
  8.         {
  9.             return View();
  10.         }
  11.     }
  12. }

Una vez hecho esto añade (en la carpeta /Views/Home) la vista Index.chstml:

  1. <html>
  2. <head><title>Nowin Test</title></head>
  3. <body>
  4.     <p>Running on Nowin… @DateTime.Now.ToShortDateString()</p>
  5. </body>
  6. </html>

Ya tenemos el controlador y la vista de MVC. Tan solo nos falta “configurar” nuestra aplicación para que use ASP.NET MVC. Para ello nos creamos una clase llamada Startup:

  1. public class Startup
  2. {
  3.     public void Configure(IBuilder app)
  4.     {
  5.  
  6.         app.UseServices(services =>
  7.         {
  8.             services.AddMvc();
  9.         });
  10.  
  11.         app.UseMvc();
  12.  
  13.     }
  14. }

El siguiente paso es añadir las referencias necesarias para hospedar nuestra aplicación web y a Nowin. Para ello editamos el archivo project.json para añadir en la sección “dependencies”:

  1. "Nowin" :  "0.11.0",
  2. "Microsoft.AspNet.Owin": "0.1-alpha-build-*",
  3. "Microsoft.AspNet.Hosting": "0.1-alpha-build-*"

(Fíjate que al guardar el fichero project.json y al compilar te saldrán ya las referencias en VS2014)

Ya tenemos Nowin agregado, ahora toca configurarlo todo. Lo primero es cargar nuestra clase Startup. Eso es necesario porque es una aplicación de consola. En este caso debemos añadir el siguiente código a Program.cs:

  1. public class Program
  2. {
  3.     private readonly IServiceProvider _hostServiceProvider;
  4.  
  5.     public Program(IServiceProvider hostServiceProvider)
  6.     {
  7.         _hostServiceProvider = hostServiceProvider;
  8.     }
  9.  
  10.     public Task<int> Main(string[] args)
  11.     {
  12.         return Task.FromResult(0);
  13.     }
  14. }

Sí: es un código un poco raro para ser un programa de consola, verdad? Por un lado Program tiene un constructor que recibe un IServiceProvider y por otro el método Main no es estático si no que devuelve una Task<int>. Y esto? Esto es porque la aplicación de consola se pone en marcha a través de KRE (el runtime de ASP.NET vNext).

Bien, ahora vamos a colocar código en Main para inicializar nuestr aplicación ASP.NET vNext:

  1. var serviceCollection = new ServiceCollection();
  2. serviceCollection.Add(HostingServices.GetDefaultServices());
  3. var services = serviceCollection.BuildServiceProvider(_hostServiceProvider);

Bien, ahora que ya lo tenemos todo configurado, nos queda poner en marcha el host. Antes que nada, un pequeño recordatorio, sobre como establecíamos el servidor web usando OWIN y Katana en una aplicación de consola. En Katana usábamos el paquete Microsoft.Owin.Hosting, que define una clase llamada WebApp<T> y usábamos un código parecido a:

var options = new StartOptions
{
    ServerFactory = "Nowin",
    Port = 8080
};
using (WebApp.Start<Startup>(options))
{
    Console.WriteLine("Running a http server on port 8080");
    Console.ReadKey();
}

En este contexto la clase Startup (parámetro genérico de WebApp) era la clase de configuración de la aplicación (lo equivalente a nuestra clase Startup). Lo interesante es la la StartOptions que permitía establecer el ensamblado que contiene el servidor web (en este caso Nowin).

En vNext la idea es muy similar (cambian clases y propiedades) pero es la misma idea:

  1. var context = new HostingContext()
  2. {
  3.     Services = services,
  4.     ServerName= "Nowin",
  5.     ApplicationName = "NowinDemo",
  6. };
  7. var engine = services.GetService<IHostingEngine>();
  8. using (engine.Start(context))
  9. {
  10.     Console.WriteLine("Server waiting… ");
  11.     Console.ReadLine();
  12. }

Nota: NowinDemo es el nombre de mi ensamblado (el que contiene la clase Startup) y Nowin es obviamente el nombre del ensamblado de Nowin.

Directamente establezco que el servidor está en el ensamblado Nowin. No obstante esto no funciona:

image

La razón es que, efectivamente ASP.NET vNext intenta ir a este ensamblado para obtener un servidor, pero ahora espera un servidor vNext y no uno OWIN como Nowin. En concreto espera que haya una clase que implemente IServerFactory (interfaz de vNext).

Bueno, por suerte, no es excesivamente complejo crear dicha clase. Así pues añadimos una clase que implemente IServerFactory:

  1.     public class NowinServerFactory : IServerFactory
  2.     {
  3.         public IServerInformation Initialize(IConfiguration configuration)
  4.         {
  5.         }       
  6.         public IDisposable Start(IServerInformation serverInformation, Func<object, Task> application)
  7.         {
  8.   
  9.         }
  10.     }

El método Initialize se llama una vez al principio de la aplicación y debemos devolver una clase que implemente IServerBuilder. Dicha interfaz tan solo define una propiedad llamada Name. Vamos a crearnos una clase que nos implemente dicha interfaz:

  1. class NowinServerInformation : IServerInformation
  2. {
  3.     public string Name { get { return "Nowin"; } }
  4. }

En el método Initialize debemos instanciar el servidor Nowin y devolver un objeto IServerInformation:

  1. public IServerInformation Initialize(IConfiguration configuration)
  2. {
  3.     var builder = ServerBuilder.New()
  4.         .SetAddress(IPAddress.Any)
  5.         .SetPort(8080)
  6.         .SetOwinApp(HandleRequest);
  7.     return new NowinServerInformation();
  8. }

Nos falta el método HandleRequest (un Func<IDictionary<string, object>, Task> que espera SetOwinApp:

  1. private Task HandleRequest(IDictionary<string, object> env)
  2. {
  3.     return _callback(new OwinFeatureCollection(env));
  4. }

La variable _callback es un Func<object, Task>. Lo interesante es el uso de la clase OwinFeatureCollection (definida en Microsoft.AspNet.Owin) que ofrece un “envoltorio” tipado al diccionario de entorno de Owin.

Ahora en el método Start del objeto IServerFactory debemos devolver el componente que es el servidor. Este método recibe el IServerInformation y la definición de aplicación (es decir el conjunto de componentes del pipeline vNext) como una Func<object, Task>.  El problema ahora lo tenemos en el hecho de que debemos obtener el servidor Nowin (INowinServer) a partir del objeto IServerInformation… pero allí no lo hemos guardado. Así pues debemos redefinir dicha clase, para que nos guarde la información necesaria para ello. La idea es que la clase que implementa IServerFactory debe ser, como su nombre indica, la factoría para permitir crear el servidor a cada petición. Así pues el código final queda como sigue:

  1. public class NowinServerFactory : IServerFactory
  2. {
  3.     private Func<object, Task> _callback;
  4.  
  5.     private Task HandleRequest(IDictionary<string, object> env)
  6.     {
  7.         return _callback(new OwinFeatureCollection(env));
  8.     }
  9.  
  10.     public IServerInformation Initialize(IConfiguration configuration)
  11.     {
  12.         var builder = ServerBuilder.New()
  13.             .SetAddress(IPAddress.Any)
  14.             .SetPort(8080)
  15.             .SetOwinApp(HandleRequest);
  16.         return new NowinServerInformation(builder);
  17.     }
  18.             
  19.     public IDisposable Start(IServerInformation serverInformation, Func<object, Task> application)
  20.     {
  21.         var information = (NowinServerInformation)serverInformation;
  22.         _callback = application;
  23.         INowinServer server = information.Builder.Build();
  24.         server.Start();
  25.         return server;
  26.     }
  27.  
  28.  
  29.     private class NowinServerInformation : IServerInformation
  30.     {
  31.         public NowinServerInformation(ServerBuilder builder)
  32.         {
  33.             Builder = builder;
  34.         }
  35.  
  36.         public ServerBuilder Builder { get; private set; }
  37.  
  38.         public string Name
  39.         {
  40.             get
  41.             {
  42.                 return "Nowin";
  43.             }
  44.         }
  45.     }
  46. }

Nota: El código de esta clase es mérito de David Fowler y lo tenéis en este Gist.

Ahora ya tan solo nos queda modificar el HostContext puesto que el ensamblado que contiene el IServerFactory ya no es Nowin sino nuestra propia aplicación (NowinDemo en mi caso):

  1. var context = new HostingContext()
  2. {
  3.     ServerName= "NowinDemo",
  4.     ApplicationName = "NowinDemo",
  5.     Services = services,
  6. };

¡Y con esto ya hemos terminado! Tenemos una aplicación ASP.NET vNext MVC6 servida desde línea de comandos y usando un servidor web OWIN como Nowin para servirla (en lugar del clásico Microsoft.AspNet.Server.WebListener de ASP.NET vNext).

¿Donde está el código compilado?

Si compilas el proyecto en VS2014 CTP verás que en la carpeta Bin/Debug… no hay nada! No hay un NowinDemo.exe ni nada parecido. Las aplicaciones ASP.NET vNext, incluso si son de consola, se ponen en marcha a través del runtime de ASP.NET. Para ello se usa el comando K run “path_de_la_aplicación”.

El fichero K.cmd está donde tengas instalado el KRE. Por defecto está en %HOME%.krepackagesKRE-svrc50-x86.0.1-alpha-build-0446bin

Si no lo tienes debes añadir al path este directorio, irte donde tienes el proyecto de VS2014 (en el path raíz del proyecto donde tienes el project.json) y teclear:

K run

¡Y listos! Tu aplicación de consola ASP.NET vNext ya está en marcha!

4 comentarios en “Sirviendo ASP.NET vNext con Nowin”

  1. Demasiada “burocracia”, aunque a mi me gusta mucho vnext y todo lo que hay detrás ejemplos como el que acabas de poner siguen dejando claro que no es todo lo sencillo hacer las cosas como debería…el que sea un CTP lógicamente es parte de este problema…

  2. Buenas Unai.

    Gracias por tus comentarios. Si, creo que has resumido bastante bien el estado de la situación…
    El principal problema ahora mismo es que al ser CTP falta bastante documentación y todos tiramos de lo mismo (el blog de loudj que has mencionado tu, el de david fowler y poco más). Y a base de prueba y error…

    Sobre lo de owin y vnext hay mucho de lo que hablar… Aquí se está cociendo una discusión bastante interesante: https://groups.google.com/forum/#!topic/net-http-abstractions/Kb22YKT0QGU

    Veremos como evoluciona todo… mientras tanto seguiremos jugando lo que se pueda 😛

Deja un comentario

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