miércoles, 30 de abril de 2014

ASP.Net MVC: Subida de imágenes al servidor y recorte con Jcrop

Recientemente tuve que incluir, en un proyecto de ASP.Net MVC en el que estoy trabajando, un formulario donde un usuario podía subir su foto al perfil, y si el sistema detectaba que el tamaño de la imagen subida superaba el 100x100, había que recortarla.

Esta premisa, aunque parece sencilla, encierra varios problemas técnicos a solucionar:

1) Subida de ficheros al servidor con ASP.Net MVC.
2) Recorte de foto y guardado.
3) Puntos a tener en cuenta: versiones de navegadores y otros aspectos técnicos.

Vamos a ir viendo paso a paso cómo implementar esta práctica funcionalidad.

Subida de ficheros al servidor con ASP.Net MVC

Para subir ficheros al servidor desde la Vista, en Mvc se dispone de la clase System.Web.HttpPostedFileBase. Para utilizarla, hay que seguir los pasos:

- Declarar una propiedad de este tipo en el Model que se va a utilizar:

public HttpPostedFileBase Fichero { get; set; }

- Crear en la vista el formulario para realizar la subida de la imagen. La propiedad Fichero del modelo en la vista se representa con un <input type="file">.

- Añadir como propiedad al formulario enctype = "multipart/form-data".

El formulario de selección se mostraría así:

Una vez que se selecciona la imagen y se sube, se trata en el Controlador correspondiente, teniendo en la propiedad Fichero el Stream de la imagen subida:

Y con esto ya se tiene el fichero subido. Como en el Controlador se ha especificado volver a dibujar la Vista, que se alimenta con un Modelo que ya contiene la imagen, se ve la imagen recién subida:


Recorte de foto y guardado

Para recortar la foto lo más sencillo es utilizar JCrop, un plugin de JQuery gratuito disponible en http://deepliquid.com/content/Jcrop.html

Una vez obtenido JCrop, se deben incluir los siguientes ficheros:


En la página web de DeepLiquid se pueden ver varios ejemplos de uso de este plugin realmente potente. A continuación se detalla cómo crear el recorte una vez que se carga la imagen recién subida con el formulario anterior, detectando el tamaño de la misma en el evento load de la imagen. El tamaño del área a recortar se establece fijo ya que no debe sobrepasar el tamaño 100x100:


Las variables que se pueden ver en la función storeCoords son también parte del Model, y en la vista serán campos ocultos que se utilizarán en el Controlador al ejecutar la acción de recortar la imagen:

Al activarse JCrop si la imagen es mayor de 100x100, el usuario debe recortarla si quiere almacenarla en el servidor:

Una vez que el usuario ha seleccionado el área que desea guardar, se ejecuta finalmente la acción de Recortar y se guarda la imagen definitiva.


Puntos a tener en cuenta

Implementar esta funcionalidad requiere tener varias cosas en cuenta:

1) Las variables x, y, height y width, utilizadas por JCrop y necesarias para poder realizar el recorte final, se definen en el Model como string, debido a que JCrop las devuelve como decimales en inglés, con separador ".". Así es más sencillo, en el Controlador, convertir a decimal con el separador del idioma actual:

decimal decX = Convert.ToDecimal(imagen.x.Replace(".", Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator));

2) Internet Explorer pone alguna que otra pega, como siempre. Por ejemplo, si se quiere utilizar el evento "load" de la imagen para detectar el ancho y alto de la misma, para mostrar la funcionalidad de Recortado en caso necesario, previamente hay que ejecutar la línea:

$("img").attr("src", $("img").attr("src"));

Esto es una solución extraída de http://garage.socialisten.at/2013/06/how-to-fix-the-ie9-image-onload-bug/. Con esto Internet Explorar ejecuta el evento "load". Hay otras soluciones utilizando también CSS, a mí ésta me pareció la más sencilla.

3) A lo largo del ejemplo, una vez guardada la imagen, tanto la inicial como la recortada, se cambia de nombre de la misma cada vez que se ejecuta un Action en el Controlador. Esto es debido a que, si se mantiene el mismo nombre de imagen en varias llamadas, aunque la imagen varíe (recortándola, por ejemplo), Internet Explorar la cachea (esto no ocurre ni en Chrome ni Firefox), por lo que siempre mostrará la primera imagen con el nombre dado. Así que para evitarnos esta molestia, utilizando los Ticks del momento actual se genera un nombre único:

string nombreFichero = Path.GetFileNameWithoutExtension(imagen.Fichero.FileName) + "_" + DateTime.Now.Ticks.ToString() + Path.GetExtension(imagen.Fichero.FileName);

4) Cuando se carga la imagen subida por el usuario, y antes de calcular si debe recortarse o no, se muestra. Normalmente cuando la imagen se muestra, se realizará dentro de un contenedor que puede tener definidos un ancho y un alto fijos (por temas de diseño, por ejemplo), por lo que la imagen que se verá en el navegador no tendrá ni su ancho ni alto reales. Para poder recortar correctamente JCrop ofrece la funcionalidad de definir el área en base a su tamaño real. Esto obliga a modificar ligeramente el ejemplo anterior:

En el modelo habrá que establecer 2 nuevas propiedades, "AnchoReal" y "AltoReal", que obtendremos de la imagen al subirla por primera vez, y que JCrop utilizará para calcular el ratio de aspecto:

5) Y por último el amigo Internet Explorer tiene reservada una sorpresa final.
La idea inicial de implementar esta funcionalidad era la de ocultar el input type="file" de Fichero, y simular el submit del formulario detectando cuando cambia este input, ya que es más cómodo para el usuario y visualmente queda más presentable.

Sin embargo, por motivos de seguridad, Internet Explorer no permite cambios en input type="file" mediante Javascript (consultar http://stackoverflow.com/questions/3935001/getting-access-is-denied-error-on-ie8/4335390#4335390 para más información). También hay varias soluciones posibles y es cuestión de probar.

Adjunto un ejemplo con todas las variantes posibles:
1) Sin redimensionar. Imagen recortando a su tamaño original.
2) Redimensionada. Imagen recortando a un ratio de aspecto, ubicada en un contenedor con tamaño fijo establecido en su estilo.
3) Sin redimensionar para navegadores no IE. Si tienes la suerte de estar completamente segur@ de que tus usuarios no utilizarán Internet Explorer, y quieres utilizar la aproximación comentada en el último punto (el input type="file" oculto y simular el submit del formulario de subida con Javascript) también dejo el ejemplo.

https://drive.google.com/drive/folders/0B7Im7mEr5csqNHFXcDBVNGROZ1U

Por supuesto se agradecen todos los comentarios.

¡¡Saludos y hasta la próxima!!

miércoles, 16 de abril de 2014

Tomcat y Eclipse: compañeros de camino II


Ahora que ya tenemos nuestro entorno de pruebas listo, vamos a preparar nuestro entorno de desarrollo.

Escribir paso a paso cómo montar este entorno es un poco latoso, y he pensado que mejor hacerlo con vídeos. Y al final he preparado dos:

a) descargas de Java y Eclipse
b) configuración del entorno de desarrollo

En el primero utilizo estas URLs:
Y únicamente me descargo la JDK y IDE para J2EE. 



 
En el segundo preparo el entorno de desarrollo en estos pasos:
  1. Instalación de la JDK de Oracle (todavía pienso en la JDK de Sun).
  2. Descompresión de Eclipse.
  3. Ejecutamos eclipse, creamos un nuevo proyecto web.
  4. A este proyecto le asignamos como servidor Tomcat7, el cual se descarga automáticamente.
  5. Creamos en el proyecto una JSP con un bucle y probamos que funciona.
Aunque en el vídeo seleccione JAVA 8, luego he tenido que retroceder por problemas de compatibilidad. Lo comento por si os pasa también a vosotros.

Está al doble de la velocidad normal, me parecía un poco pesado ver cómo se descomprime un zip a velocidad normal.

Aquí lo tenéis:



Con esto la creación de los entornos ya está hecha.

Lo siguiente que quiero hacer es un pequeño proyecto de navegación clásica de JSP a Servlet y de Servlet a JSP, y desplegarlo en el entorno de pruebas.

Nos vemos.