martes, 18 de marzo de 2014

REST, HTTP request mediaType

Como sabéis últimamente el término REST se está extendiendo bastante porque es una técnica más ligera en aspectos de comunicación que por ejemplo SOAP y más sencillo de implementar lo cual facilita las cosas para trabajar contra un back-end remoto desde dispositivos móviles con lenguajes como podrían ser javascript.

A priori no sólo vale con exponer un servicio por HTTP y consumirlo para decir que estamos haciendo REST ya que es toda una filosofía y contra más sigues las pautas más RESTFul es tu API y más molas. En resumen REST se basa en HTTP para comunicar 2 extremos así que tenemos varios aspectos que pueden variar y hay recomendaciones o buenas prácticas pero en definitiva todo queda bastante abierto a la persona/s que está implementando la API y te puedes encontrar de todo. Por ejemplo, centrándonos en como ENVIAR datos en una petición/request tenemos varios modos de codificar esta información. El valor típico y por defecto para MediaType es form-url/encoded pero podríamos cambiar este comportamiento en cliente al realizar una petición si la situación lo requiere.

Form-url/encoded no envía nada en el payload (cuerpo de la petición HTTP) se pasa todo codificado en la URL como una colección de clave-valor. Esto puede darte problemas cuando quieres enviar mucha información ya sea en un objeto complejo con sub-objetos y/o listas o directamente un binario pesado.

Hace un tiempo me tocó hacer un cliente a toda pastilla donde no tenía control sobre la parte servidor y estaba obligado a enviar archivos PDF a servidor (verbo GET) codificados en la URL. Todo esto era un requisito para que llegase el archivo a servidor ya que imagino que leía directamente de la URL e ignoraba headers. Poderse hacer se puede pero tal vez si hubiese tenido control del server hubiese trasteado para aceptar codificación MultiData en servidor y desde cliente enviaría la info en el payload lo cual en este caso considero que sería lo recomendable. Además si enviamos mucha info en la URL en .Net por defecto tendremos problemas con el serializador que usa System.Net.Http aquí se explica cómo saltarse la limitación.

Otra pega que veo si quieres enviar de golpe un objeto complejo que a su vez contiene alguna sublista o subobjeto con más atributos. Con json podríamos representar esta jerarquía pero con listas de clave-valor como sería querystring tendríamos que montarnos nosotros algún modo de hacerlo. Aquí la propuesta que pasaba en su día (serialiceListAsParamArray) un poco de aquella manera. Ahora estoy trabajando contra una web api que estamos desarrollando nosotros y no tenemos ese problema ya que el servidor acepta peticiones de todo tipo siempre que el mediaType esté correctamente informado en el header de la petición HTTP.

En su día me creé un cliente genérico que poco a poco voy puliendo, veamos como quedaría el método POST que ahora también contempla que desde cliente se pueda pasar la información en formato json.
private HttpResponseMessage Post(T pValue, bool serialiceListAsParamArray, bool ignoreURLEncondingSizeLimit, bool returnResponseMessage, RequestMediaTypeHeader pMediaType)
{
  try
  {
    HttpContent content = null;

    string pURLEncodedContent = "";
 
    switch (pMediaType)
    {
      #region FormEncodedURL
        case RequestMediaTypeHeader.FormEncodedUrl:
          List> oListTest = new DgestJavaScriptSerializer().GetKeyValuePair(pValue, serialiceListAsParamArray);
          if (!ignoreURLEncondingSizeLimit)
            content = new FormUrlEncodedContent(oListTest);
          else
          {
            foreach (KeyValuePair item in oListTest)
               pURLEncodedContent += string.Format("{0}={1}&", System.Web.HttpUtility.UrlEncode(item.Key), System.Web.HttpUtility.UrlEncode(item.Value));

            pURLEncodedContent = pURLEncodedContent.Substring(0, pURLEncodedContent.Length - 1);

            content = new ByteArrayContent(this.ConvertStringToByteArray(pURLEncodedContent));
            content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
          }
          break;
       #endregion

       #region JSON
         case RequestMediaTypeHeader.Json:
           string json = new DgestJavaScriptSerializer().Serialize(pValue);
           content = new ByteArrayContent(this.ConvertStringToByteArray(json));
           content.Headers.ContentType = new MediaTypeHeaderValue(this.GetMediaTypeHeader(pMediaType));
           break;
       #endregion
     }

     HttpResponseMessage response = _Client.PostAsync(_ServiceName, content).Result;
     if (!response.IsSuccessStatusCode)
     {
       throw new Exception(string.Format("{0}: No se ha podido conectar al servicio.", response.StatusCode), new Exception(response.ReasonPhrase)); ;
     }
     return response;
   }
   catch (Exception ex)
   {
     throw ex;
   }
}
Veamos una llamada desde C#:
public ActionResult CalcularProvision(Simulador simulador)
{
  //llamada a Servicio
  simulador = new RESTClient(new Uri(_urlWebApi), "Simulador/Calcular").Post(simulador, RESTClient.RequestMediaTypeHeader.Json);

  return Json(simulador.Provisiones);
}
Hasta aquí el artículo de hoy, como siempre comentaros que podéis seguir areaTIC en las redes sociales y todos vuestros comentarios, críticas y aportaciones son bienvenidas. Que vaya bien la semana!

martes, 11 de marzo de 2014

Excepciones en MVC – HandleErrorAttribute Filter

Esta semana en areaTIC nos disponemos a evaluar las posibilidades que ofrece ASP.NET MVC para la gestión de excepciones. En nuestro caso siempre habíamos optado en entornos WebForms por soluciones globales tipo Application_Error más que ir página a página. En MVC tenemos alguna que otra posibilidad más.

Veamos que opciones tenemos:
  • A nivel local en cada acción de cada controlador con bloque try catch. Implica controlar el error cada vez que creamos una acción en un controlador.

  • A nivel local también podríamos sobre escribir el método OnException del controlador y de este modo evitaríamos tener que escribir un bloque Try/Catch por cada acción.

  • A nivel global si aplicamos mediante FilterConfig un filtro a todos los controladores (por defecto la plantilla de Visual Studio lo aplica) podemos especificar entonces vía web.config una página a la que redirigir la petición cuando se produce un error en un controlador y una vez en la página mostrar el error. Por defecto no se puede gestionar el logging ni tratar diferente los errores según el escenario (por ejemplo llamadas Ajax). Además todo error que se produzca fuera de MVC como podría ser un 404 no pasará por este filtro. Para salvar las 2 primeras limitaciones tendríamos que extender HandleErrorAttribute y para redirigir el 404 a la página de error se podría hacer vía web.config también.

  • Otra opción es usar Application_Error de Global.asx. Tiene sus inconvenientes, el principal es que el evento salta fuera de contexto MVC y por tanto es más complicado personalizar la excepción en función de la acción que la origina.

En nuestro caso nos decantamos por Filters, veamos como extender HandleErrorAttribute. Los pasos para crear un Filter personalizado son muy parecidos a los que haríamos para crear un Helper propio. Creamos una carpeta Filters en el proyecto (si no existe) y dentro añadimos la clase que queremos extender. En este caso heredamos de HandleErrorAttribute e implementamos los métodos base y sobreescribimos el método OnException(). Nos creamos también un método privado para determinar si la petición es Ajax o no ya que en función de esto devolveremos vía MVC la página de error o bien responderemos a la petición inicial Ajax con el código de error correspondiente y el contenido del mensaje para poder gestionar el error en cliente como necesitemos.
namespace AreaTIC.Web.Filters
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
    public class AreaTICHandleErrorAttribute : HandleErrorAttribute  
    {
        public override void OnException(ExceptionContext filterContext)
        {
            if (IsAjax(filterContext))
            {
                filterContext.Result = new JsonResult()
                {
                    Data = filterContext.Exception.Message,
                    JsonRequestBehavior = JsonRequestBehavior.AllowGet
                };

                filterContext.ExceptionHandled = true;
                filterContext.HttpContext.Response.StatusCode = 500;
            }else
                base.OnException(filterContext);
        }

        private bool IsAjax(ExceptionContext filterContext)
        {
            return filterContext.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest";
        }
    }
}
Por último en app_start en el archivo FilterConfig.cs veréis que hay una línea que se encarga de automáticamente añadir el filtro HandleErrorAttribute a cada acción de todos los controladores donde se ha de especificar el nuevo filtro extendido que acabamos de crear.
namespace AreaTIC.Web
{
    public class FilterConfig
    {
        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new AreaTICHandleErrorAttribute());
        }
    }
}
Ahora todo correcto si el error se produce durante una llamada Ajax se lanza el evento error: de la llamada Ajax y allí podemos mostrar el contenido del error en un popup o realizar las acciones que creamos oportunas. En cambio si el error se genera desde una petición HTTP normal sin Ajax MVC responde con la página de error que tenemos configurada en Web.Config en la sección CustomErrors.

Hasta aquí el artículo de hoy, como siempre recordar que podéis seguir areaTIC en las redes sociales y son bienvenidos todo tipo de aportaciones y/o comentarios. Que paséis una buena semana!

miércoles, 5 de marzo de 2014

¿Qué es BOM?

BOM Byte order mark se usa en ámbito de codificación de archivos y representa una combinación de bytes que proporciona información sobre como están ordenados el resto de bytes en una secuencia de datos unicode.

En escenarios de intercambio de archivos es recomendable controlar este aspecto y sólo incluir la marca BOM si realmente es lo pactado entre las partes.

El intercambio de archivos contra otra aplicación es un escenario muy habitual, sin ir más lejos ahora consultando este blog tu navegador se está 'entendiendo' con el servidor de donde descarga el contenido para que los archivos sean legibles y poder mostrarte el este artículo... Es difícil encontrar contenido no legible desde un navegador actual en internet ya que cada vez más, los fabricantes de sistemas operativos usan formatos que permiten un margen más amplio de caracteres que ASCII o UTF-8 con lo que apuestan por una codificación global.

Es más fácil encontrarse el problema en un escenario donde tienes que programar una llamada a un servicio web o similar enviando contenido XML y el servidor se queja del BOM al intentar leer el mensaje. Esto se debe a que dependiendo de la codificación, BOM puede ser obligatorio, opcional o no producir ningún efecto. En función del sistema operativo puede ser que trate una situación u otra de modo diferente, por ejemplo en UTF-8 incluir BOM es opcional pero posiblemente algún des-serializador de problemas al leer el contenido si se incluye. Hay codificaciones que si no se ha incluido la información de marcado podrían generar algún carácter no deseado como un espacio en blanco al recibir el fichero.

Por tanto es interesante saber como especificarle al serializador la codificación del contenido y si queremos incluir BOM o no. Veamos un ejemplo sobre como alterar esto en .NET usando XmlSerializer.
  XmlWriterSettings settings = new XmlWriterSettings();
  settings.Encoding = new UTF8Encoding(false);

  using (XmlWriter oWriter = XmlWriter.Create(path,settings))
  {
     XmlSerializer oSerializer;
     oSerializer = new XmlSerializer(typeof(Document));
     oSerializer.Serialize(oWriter,mDocuementSepa);
  }
Mediante XmlWriterSettings podemos acceder al atributo Encoding donde especificamos la codificación. Fijaros en el constructor de UTF8Encoding especificamos el parámetro encoderShouldEmitUTF8Identifier a false para evitar que se incluya BOM.

Si queréis una explicación más técnica sobre que es BOM y como influye exactamente os paso esta página donde está bastante bien explicado y traducido. Hasta aquí el artículo de hoy, recordar que podéis seguir areaTIC en las redes sociales. Si quieres participar en areaTIC puedes ponerte en contacto con nosotros a través del form de contacto. Hasta la próxima!