Tipos de respuesta y plantillas en NancyFx

En los artículos anteriores de la serie, hacíamos un breve repaso a cómo crear módulos y rutas con NancyFx, en este veremos cómo responder a las peticiones usando texto plano, JSON o ficheros, así como usar un viewengine que nos permita, a través de plantillas, usar elementos generados por el servidor en documentos HTML.

Presentando al Content-Type

El Content-type es un mensaje que forma parte de los encabezados HTTP y especifica el tipo de recurso que soliticamos y que se devuelve en una respuesta para que el navegador lo pueda procesar. De esa manera, un content-type de tipo text/html se procesará en el cliente como un fichero HTML a renderizar, el tipo application/octet-stream se referirá habitualmente a un fichero binario, y así para ficheros de audio, vídeo u otros.

Nancy permite especificar el tipo de contenido que estamos devolviendo, de esta manera podemos rescatar el ejemplo anterior donde especificábamos el código de retorno, y modificarlo para que devuelva un contenido con un tipo mime específico:

public class ResponseTypes : NancyModule
{
	public ResponseTypes ()
	{
		Get["/responses/text"] = _ => {
			return ((Response)"<h1>Texto plano</h1>\n").
				WithContentType("text/plain");
		};

		Get["/responses/html"] = _ => {
			return ((Response)"<h1>Texto HTML</h1>\n").
				WithContentType("text/html");
		};

		Get["/responses/binary"] = _ => {
			return ((Response)"<h1>Texto en un fichero</h1>\n").
				WithContentType("application/octet-stream");
		};
	}
}

En este caso, la primera respuesta, aunque espefiquemos código HTML, mostrará el texto plano, en la segunda hemos especificado que se trata de html, con lo cual la respuesta se renderizará por pantalla. En la tercera respuesta, hemos establecido el tipo a binario, con lo cual la acción a realizar será la de descargar el contenido a un fichero en disco.

JSON

Javascript Object Notation, o JSON, es un viejo conocido de este blog, ya que lo hemos usado para acceder a Azure Mobile Services, a Twitter, y a otros. Es una representación ligera de datos que tiene un formato similar a este:

{
    "firstName": "John",
    "lastName": "Smith",
    "age": 25,
    "address": {
        "streetAddress": "21 2nd Street",
        "city": "New York",
        "state": "NY",
        "postalCode": 10021
    },
    "phoneNumbers": [
        {
            "type": "home",
            "number": "212 555-1234"
        },
        {
            "type": "fax",
            "number": "646 555-4567"
        }
    ]
}

Una de las cosas que nos permite NancyFx, es codificar en formato Json un objeto (o un conjunto de ellos) para devolverlos al cliente. Veamos un ejemplo:

public class Person
{
	public string Name {
		get;
		set;
	}
	public string Surname {
		get;
		set;
	}
}public class JsonResponse : NancyModule
{
	public JsonResponse ()
	{
		Get["/json/single"] = _ => Response.AsJson(
			new Person()
			{
				Name = "Roberto",
				Surname = "Luis"
			}
		);
	}
}

En este ejemplo, creamos un objeto, y usando Response.AsJson codificamos su contenido en formato Json y lo devolvemos. La respuesta, si la viésemos en un navegador, tendría este aspecto:

{
    "Name": "Roberto",
    "Surname": "Luis"
}

Si examinamos cuidadosamente la cabecera de la respuesta (usando curl por ejemplo), veremos además el valor del campo Content-Type:

Content-Type: application/json; charset=utf8

Lo que ha ocurrido es que Nancy automáticamente ha gestionado las cabeceras por nosotros. Contamos además con métodos como AsXml, AsImage o AsFile para devolver nuestros objetos en otros formatos.

ViewEngines

Un view engine, o motor de vistas, es un componente que nos permite combinar plantillas HTML con llamadas al servidor, evitandonos el engorro de trabajar directamente con html dentro del código, y posiblitando una separación de responsabilidades. Nancy incluye uno por defecto, llamado “Super Simple View Engine”, que permite tener una plantilla de este estilo:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8"> <title>External template</title>
  </head>
  <body>
    <h1>Hello, @Model.Name!</h1>
    <ul>
    @Each.Model.Children
    <li>@Current</li>
      @EndEach
    </ul>
  </body>
</html>

Los campos @Model.Name y @Model.Children, nos pueden recordar a Razor, el motor de vistas de ASP.net MVC3, mientras el uso de @Each.Model nos pueden recordar a las plantillas erb de ruby. Estos elementos nos permiten agregar datos de manera dinámica a un documento HTML.

La variable @Model, a la que hemos accedido desde la vista, se define desde el controlador, y puede ser un objeto perteneciente a una clase, o como se muestra en el ejemplo, un objeto anónimo que contenga los datos que necesitamos:

public class ViewTemplates : Nancy.NancyModule
{
	public ViewTemplates ()
	{
		Get ["/ViewEngines/WithData"] = _ => {
			var model = new { 
				Name = "John Smith", 
				Children = new []{"Mike", "Bob", "James"}
			};
			return View["ViewEngines/TemplateWithData.html", model];
		};
	}
}

Finalmente, se devuelve la vista usando la ruta relativa de la misma, y el objeto que servirá de modelo.

Resumen

En este artículo se ha visto cómo podemos personalizar el tipo de respuesta, ya sea texto, binario, o JSON, así como usar un motor de vistas para separar la vista del controlador. Con este artículo terminamos esta serie de introducción a NancyFx, si deseas consultar más información, aquí están los artículos anteriores de la serie:

Enlaces adicionales

Módulos y rutas en NancyFx

Este artículo continúa la serie de introducción a Nancy, un framework para aplicaciones web escrito en C#. En el artículo anterior (Primer contacto con NancyFX) hacíamos una breve introducción a la sintaxis, y veíamos un ejemplo muy básico de rutas. En este veremos con un poco más de detalle qué es un módulo, cómo funciona el sistema de rutas, cómo definirlas, y qué tipo de respuestas podemos dar.

Módulos

using System;

namespace NancyFxSinatra
{
	public class SampleModule : Nancy.NancyModule
	{
		public SampleModule()
		{
			Get["/"] = _ => "Hello World!";
		}
	}
}

En el artículo anterior mostrábamos el siguiente código. Las rutas están definidas en el constructor de un módulo, que es a su vez la pieza clave que define el comportamiento de nuestra aplicación.

Un módulo hereda de la clase NancyModule y se puede definir en cualquier parte de la aplicación. No es necesario declararlos explícitamente ya que Nancy se encargará de buscarlos por nosotros. Esta búsqueda se realiza al iniciar la aplicación, quedando en caché una vez encontrados.

Esto nos permitiría, en una hipotética aplicación de gestión tener nuestro ProductModule, que gestionara productos, y nuestro CategoriesModule que gestionara categorías. Los módulos pueden encontrarse en diferentes carpetas, o incluso en diferentes ensamblados.

Rutas

Todos los caminos conducen a Roma

Una ruta se compone por:

Método + Patrón + Acción.

Método

El método es el verbo HTTP por el que accedemos a un recurso. A continuación podemos ver un ejemplo de acceso a diferentes recursos con el mismo patrón.

using System;

namespace NancyFxSinatra
{
	public class Routes : Nancy.NancyModule
	{
		public Routes ()
		{
			Get["/routes"] = _ => "Respuesta usando GET\n";
			Post["/routes"] = _ => "Respuesta usando POST\n";
		}
	}
}

Para probarlo en este caso usaremos la línea de comandos y la utilidad curl, que permite hacer peticiones a una pagina, y devuelve el resultado. Se encuentra disponible para Mac y Linux de manera nativa, y para Windows lo podemos obtener de su página oficial.

Si queremos probar la llamada por GET, usamos el comando:

curl -X GET http://127.0.0.1:8080/routes

Y la salida que obtendremos será:

Respuesta usando GET

Por otra parte, si queremos probar la llamada por POST, el comando será:

curl -X POST http://127.0.0.1:8080/routes

Con la correspondiente salida:

Respuesta usando POST

Así podemos probar el resto de métodos HTTP soportados, que son GET, POST, PUT, DELETE, PATCH, HEAD y OPTIONS.

Patrón

El patrón, por su parte, nos permite detectar una url específica, así como capturar elementos. Podemos destacar los siguiente patrones:

  • “/users”: Detecta la coincidencia exacta.
  • “/users/{id}”: Al caso anterior, captura además cualquier elemento que se pase a continuación. En el caso de “/users/roberto”, id tendría el valor de roberto.
  • “/users/{id}/{format?}”: Además de lo anterior, detecta una ruta que puede contener, o no ese valor. En el caso de “/users/roberto/json”, format tendría el valor de json. Si por lo contrario usamos una ruta de “/users/roberto” también será capturada, ya que el parámetro es opcional.
  • “users/{cur*}”: A diferencia del anterior, solamente detecta y captura entradas que comiencen por cur, por lo tanto “/users/roberto” no sería detectado, pero “/users/cur1239” sí.

Podemos encadenar patrones como hemos visto antes para crear rutas más complejas, y usar expresiones regulares para una captura más específica.

Acción

Las acciones son el contenido de nuestra llamada, es decir, el resultado de la misma, es de tipo Response, sin embargo, según la respuesta del método se puede devolver:

  • entero: Se interpreta entonces como código de estado HTTP (ejemplo, 404).
  • HttpStatusCode: Código de respuesta incluido en Nancy, que corresponde también a estados HTTP.
  • texto: Interpretado como el cuerpo de la respuesta.

Para demostrarlo, veamos 4 tipos de respuesta, las 3 primeras son las que hemos mostrado, mientras que la última devuelve un error y además de una respuesta en el cuerpo.

Get ["/actions/ok"] = _ => 202;
Get ["/actions/auth"] = _ => HttpStatusCode.Forbidden;
Get ["/actions/text"] = _ => "Hola Mundo";Get["/actions/error"] = _ => {
    Response res =  ((Response)"Error con mensaje\n");
    res.StatusCode = HttpStatusCode.InternalServerError;
    return res;
};

Usando curl (con la opción -v para que muestre también las cabeceras) podemos comprobar que el resultado de la petición corresponde con los códigos que hemos mostrado.

Resumen

En este artículo hemos visto qué son los módulos, cómo, usando comandos HTTP, podemos especificar la manera para acceder a un recurso. Hemos visto también, cómo capturar rutas específicas o que cumplan con algún criterio, y finalmente, hemos devuelto mensajes HTTP usando números y enumerados. En el próximo artículo veremos cómo usar ViewEngines y plantillas HTML para contenido.

Más información:

Primer contacto con NancyFX: Desarrollo web ligero con C#

Existen multitud de frameworks para el desarrollo web, y posiblemente hayas oído hablar de Symfony para PHP, Django para Python, ASP.net MVC para plataformas basadas en .NET. Estas herramientas contienen un gran número de características para crear aplicaciones, se basan en convenciones, y existe abundante literatura sobre ellos.

Por otro lado, hay otros que se caracterizan por ser más livianos, de tal manera que contienen un número más reducido de características que sus hermanos mayores y que nos pueden ser útiles para crear soluciones más específicas. En esta serie de artículos haremos una breve introducción a Nancy, un framework para aplicaciones web escrito en C#.

¿Por qué Nancy?

Una de las cosas que más me ha llamado la atención es que está inspirado en Sinatra, un DSL (Lenguaje específico de dominio) escrito en Ruby. Además, como la mayoría de frameworks, es Open Source, lo que nos permite ver bajo el capó e intentar aprender cómo sucede la “magia”.

Sinatra es bastante conocido en la comunidad Ruby, y ha servido de ejemplo para otros, aparte de Nancy. Uno de los libros más interesantes y sencillos es Sinatra up and running. de Alan Harris y Konstantin Haase, este último miembro del equipo que da mantenimiento a Sinatra. Ellos han permitido amablemente que pueda portar los ejemplos del mismo de ruby a C#, y veremos algunos a lo largo de esta serie, que cubren temas como el ya clásico “Hola mundo”, el uso de rutas, plantillas, viewengines, cookies y errores entre otras. El código de los ejemplos está disponible en github.

Instalación

Para instalar Nancy necesitamos Visual Studio 2010+ con nuget, o bien Xamarin Studio/Monodevelop 4.1+. Para el ejemplo crearemos un sitio web ASP.net vacío. Una vez creado, y usando nuget podemos usar el siguiente comando:

Install-Package Nancy

Esto agregará las dependencias de Nancy en nuestro proyecto. Un último detalle que necesitamos antes de poder empezar a escribir código, es editar nuestro fichero web.config para que tenga este aspecto:

 <?xml version="1.0" encoding="utf-8"?>
 <configuration>
   <system.web>
     <httpHandlers>
       <add verb="*" type="Nancy.Hosting.Aspnet.NancyHttpRequestHandler" path="*" />
     </httpHandlers>
     <compilation>
       <assemblies />
     </compilation>
   </system.web>
   <system.webServer>
     <validation validateIntegratedModeConfiguration="false" />
     <handlers>
       <add name="Nancy" verb="*" type="Nancy.Hosting.Aspnet.NancyHttpRequestHandler" path="*" />
     </handlers>
   </system.webServer>
 </configuration>

Una vez tengamos esta configuración inicial, podemos escribir nuestro “Hola mundo”. En el código que se muestra a continuación, dentro del constructor definimos una ruta, así como el verbo HTTP que usaremos para la misma, y el resultado que devolveremos.

using System;

namespace NancyFxSinatra
{
	public class SampleModule : Nancy.NancyModule
	{
		public SampleModule()
		{
			Get["/"] = _ => "Hello World!";
		}
	}
}

Si compilamos y ejecutamos nuestro módulo (F5 en Visual Studio y cmd + Enter en Xamarin Studio) veremos algo así:

helloNancy

Puede que no parezca gran cosa, pero en pocas líneas hemos definido un módulo, y nos hemos asociado a una ruta específica con un verbo concreto. Si por ejemplo intentásemos hacer una petición a otra ruta tendríamos este divertido mensaje 404.

nancy404

Este es un ejemplo muy sencillo (y poco útil) de lo que se puede hacer. Podemos usar parámetros como parte de la ruta, lo cual nos da un poco más de juego, por ejemplo si al constructor anterior agregamos la siguiente línea:

Get ["/hola/{name}"] = _ => "Hola " + _.name;

Entonces la aplicación reconocerá también /name/nombre, siendo nombre cualquier texto. Con esto podemos ya diseñar una pequeña API REST, ya que tenemos, por una parte, soporte para verbos HTTP, rutas específicas, e indicadores de recursos.

En el próximo artículo veremos además cómo pasar (y procesar) argumentos vía Post, y cómo devolver errores específicos al navegador, entre otras cosas.

Happy hacking!

Más información:

Azure Mobile Services bajo el capó: Insertando elementos

Azure Mobile Services nos proporciona servicios de backend en la nube para Windows Phone, Windows 8, iOS, Android y plataformas basadas en web. Permite además agregar seguridad, autenticación y notificaciones de manera sencilla y rápida. Pero, cómo funciona realmente?

Para averiguarlo, podemos recurrir a la documentación que tenemos en MSDN, aunque también podemos ver directamente el código fuente.

La API que se nos ofrece es open source, con lo cual podemos no solamente ver el código fuente sino además poder realizar contribuciones al mismo.

En este artículo quiero hacer un breve recorrido por la API, para una operación muy concreta, insertar elementos. En otros artículos de esta serie veremos cómo hacer consultas, o qué pasa del lado de servidor.

Para este artículo usaremos los siguientes elementos:

Los ejemplos los veremos con el SDK de Windows Phone 8, pero podemos ver que existe un código similar para el resto de plataformas compatibles.

De un vistazo

La API de Azure Mobile Services está compuesto por objetos que nos ayudan a:

  • Convertir nuestros objetos a formato JSON para su envío y hacer la operación contraria a la recepción (Serialización)
  • Realizar solicitudes al servicio que hemos creado en Azure mediante comandos HTTP usando una arquitectura REST
  • Gestionar autentificación y seguridad para las mismas.

El código fuente del SDK para Windows Phone 8 contiene varios elementos

  • Tests que aseguran su integridad (a nivel de core como a nivel de cada cliente en particular).
  • Código manejado (es decir, código en .NET tradicional).
  • Código nativo, dentro de los espacios de nombres de WinMD (Recordemos que Windows Phone 8 tiene soporte para las API nativas de WinRT).

Para poder trabajar con Mobile Services necesitamos en primer lugar una referencia a la tabla:

private MobileServiceCollection<TodoItem, TodoItem> items;
private IMobileServiceTable<TodoItem> todoTable = 
    App.MobileService.GetTable<TodoItem>();

Este método está situado en la clase MobileServiceClient que contiene otros que veremos a lo largo del artículo:

Este método está situado en la siguiente ruta:

src/
  Microsoft.Azure.Zumo.WindowsPhone8.Managed/
    Core/
      MobileServiceClient.Managed.cs

El código es el siguiente:

public IMobileServiceTable GetTable()
{
    SerializableType type = SerializableType.Get(typeof(T));
    return new MobileServiceTable(type.TableName, this);
}

En primer lugar obtiene un tipo serializable específico, que es el que se usará para poder transformar el objeto en una tabla en el servidor. Luego generará una tabla de tipo MobileServiceTable con el nombre creado a partir de este tipo. Hasta aquí solamente hemos hecho trabajo en local, es decir, no ha habido conexión con el servicio.

Veamos qué pasa al insertar un elemento en la tabla:

await todoTable.InsertAsync(todoItem);

El código para esta función se encuentra en la siguiente ruta:

src/
  Microsoft.Azure.Zumo.WindowsPhone8.Managed/
    Core/
      MobileServiceTable.Generic.cs

En este código lo que vemos es lo siguiente:,

  • No podemos insertar elementos nulos,
  • Se obtiene un JObject (un objeto serializado en formato JSON) a partir de MobileServiceTableSerializer
  • Se envía una petición ya con el objeto serializado
  • Se recibe el resultado de la petición (en formato JSON)
  • Finalmente, se convierte el objeto recibido en el enviado, por si los datos hubiesen cambiado.
public async Task InsertAsync(T instance)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }

            // Serialize the instance
            JObject value = MobileServiceTableSerializer.Serialize(instance).AsObject();

            // Send the request
            JToken response = await this.InsertAsync(value);

            // Deserialize the response back into the instance in case any
            // server scripts changed values of the instance.
            MobileServiceTableSerializer.Deserialize(response, instance);
        }

El envío de la petición se hace en dos fases, una fase manejada, y otra fase nativa, la fase manejada se hace en un fichero situado en:

src/
  Microsoft.Azure.Zumo.WindowsPhone8.Managed/
    Core/
      MobileServiceClient.Managed.cs

En el que se realiza la siguiente operación:

public Task<JToken> InsertAsync(JObject instance)
{
    return this.SendInsertAsync(instance);
}

Que nos lleva a este otro fichero (situado en la fase manejada):

internal async Task SendInsertAsync(JObject instance)
{
    if (instance == null)
    {
        throw new ArgumentNullException("instance");
    }

    // Make sure the instance doesn't have its ID set for an insertion
    if (instance.Get(IdPropertyName) != null)
    {
        throw new ArgumentException(
            string.Format(
                CultureInfo.InvariantCulture,
                Resources.CannotInsertWithExistingIdMessage,
                IdPropertyName),
            "instance");
    }

    string url = GetUriFragment(this.TableName);
    JToken response = await this.MobileServiceClient.RequestAsync("POST", url, instance);
    JToken patched = Patch(instance, response);
    return patched;
}

Aquí las operaciones realizadas son:

  • Comprobar que el elemento que estamos insertando no tenga ya un Id, al fin y al cabo es nuevo.
  • Obtener la url de la tabla a la que vamos a insertar el elemento.
  • Enviar una petición HTTP POST al servicio web, con la url que hemos especificado y los datos, y recuperar el resultado.
  • Actualizar el objeto enviado con la respuesta.
  • Devolver el objeto enviado.

Finalmente llegamos a RequestAsync, donde se realiza la petición, situada en este fichero:

src/
  Microsoft.Azure.Zumo.WindowsPhone8.Managed/
    Core/
      MobileServiceClient.cs

Este método genera una petición del tipo deseado, especifica que solamente queremos parámetros en formato JSON, especifica la autenticación a partir de las claves que hemos especificado en la configuración inicial, y finalmente envía la petición:


internal async Task<JToken> RequestAsync(string method, string uriFragment, JToken content)
        {
            Debug.Assert(!string.IsNullOrEmpty(method), "method cannot be null or empty!");
            Debug.Assert(!string.IsNullOrEmpty(uriFragment), "uriFragment cannot be null or empty!");

            // Create the web request
            IServiceFilterRequest request = new ServiceFilterRequest();
            request.Uri = new Uri(this.ApplicationUri, uriFragment);
            request.Method = method.ToUpper();
            request.Accept = RequestJsonContentType;

            // Set Mobile Services authentication, application, and telemetry
            // headers
            request.Headers[RequestInstallationIdHeader] = applicationInstallationId;
            if (!string.IsNullOrEmpty(this.ApplicationKey))
            {
                request.Headers[RequestApplicationKeyHeader] = this.ApplicationKey;
            }
            if (!string.IsNullOrEmpty(this.currentUserAuthenticationToken))
            {
                request.Headers[RequestAuthenticationHeader] = this.currentUserAuthenticationToken;
            }

            // Add any request as JSON
            if (content != null)
            {
                request.ContentType = RequestJsonContentType;
                request.Content = content.ToString();
            }

            // Send the request and get the response back as JSON
            IServiceFilterResponse response = await ServiceFilter.ApplyAsync(request, this.filter);
            JToken body = GetResponseJsonAsync(response);

            // Throw errors for any failing responses
            if (response.ResponseStatus != ServiceFilterResponseStatus.Success || response.StatusCode >= 400)
            {
                ThrowInvalidResponse(request, response, body);
            }

            return body;
        }

Es todo un conjunto de acciones que se llevan a cabo solamente para insertar un elemento. En el próximo artículo veremos cómo se ejecutan las consultas usando la API, y cómo funcionan métodos como este:

List<TodoItem> items = await todoTable
   .Where(todoItem => todoItem.Complete == false)
   .ToListAsync();

Happy Hacking!