miércoles, 19 de agosto de 2015

AngularJS, Cordova, Asp.net - Imagen cámara a api

Hoy veremos un ejemplo sobre cómo tratar una imagen tomada con el plugin de Cámara de PhoneGap y subirlo a una web api usando ng-resource de AngularJS.

La solución que aporto aquí es una opción entre varias posibles, en mi caso quería usar ng-resource para enviar la imagen y no tener que usar el plugin FileTransfer de Cordova / Phonegap. El ejemplo podría ampliarse y mejorarse para controlar el progreso de subida o cancelar una subida en curso… pero bueno comparto código por si a alguien busca algo sencillo que funcione sin demasiada floritura.

Para el código servidor de la API, he descargado este ejemplo de msdn y he aprovechado la parte de web api en concreto la implementación de la acción PostAsync del PicController. Veréis que la api espera contenido de tipo MultiPart Form/Data. Sobre el ejemplo he descartado la parte de AngularJS porque está más trabajado de lo que necesito en mi caso y en el ejemplo usa <input file> mientras que nosotros necesitamos subir un archivo obtenido de la cámara del dispositivo.

En mi escenario quiero enviar en la misma petición a servidor tanto la imagen como su información en el contexto de aplicación, es decir, un objeto pic que contenga información necesaria para clasificar la imagen en el contexto de mi aplicación.

Cliente – ng-Resource

El modo más sencillo de enviar contenido multipart-data a server es usando el objeto FormData de html (como harías en el ejemplo de msdn con <input file>). Con transformRequest podemos crearnos una función donde gestionamos la creación del form e incluimos tanto el fichero como la información.
     app.factory("PicResource", function ($resource) {
        return $resource(
          protocol + "://" + host + "/api/pic/:Id",
          { Id: "@Id" },
          {
              "update": { method: "PUT" },
              "save": {
                  method: 'POST',
                  transformRequest: function (pic) {
                      var formData = new FormData();
                      formData.append("file", pic);
                      formData.append("data", pic.model);
                      return formData;
                  },
                  headers: { 'Content-Type': undefined }
              }
          }
       );
    });


Cliente – Camera

Configuramos el plugin de cámara como data_url.
  function onPictureSucces(image) {
     image = dataURItoBlob("data:image/jpeg;base64," + image);
     image.model = "{'Name': 'test', 'Type':0, 'Default':false, 'ThumbnailURL':'' }";
     picResource.save(photofile).$promise.then(function (response) {
        alert('fichero subido ok');
     }, function (error) {
        alert('se ha producido un error al actualizar los datos');
     });
  }

  function dataURItoBlob(dataURI) {
    // convert base64/URLEncoded data component to raw binary data held in a string
    var byteString;
    if (dataURI.split(',')[0].indexOf('base64') >= 0)
       byteString = atob(dataURI.split(',')[1]);
    else
       byteString = unescape(dataURI.split(',')[1]);

       // separate out the mime component
       var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

       // write the bytes of the string to a typed array
       var ia = new Uint8Array(byteString.length);
       for (var i = 0; i < byteString.length; i++) {
          ia[i] = byteString.charCodeAt(i);
       }
       return new Blob([ia], { type: mimeString });
  }


Server

Al ejemplo que hago referencia al inicio de este articulo únicamente he añadido código para desearializar el json de Pic siguiendo las instrucciones del autor del ejemplo de msdn por tanto evito pegar aquí el código.

Espero a alguien le sea de ayuda, cualquier duda o comentario ya sabéis que siempre es bienvenido en areaTIC. Hasta la próxima!