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!!

Comentarios

  1. que librerías necesito utilizar. Saludos

    ResponderEliminar
  2. ¡Hola! Pues tal y como comento, el plugin a utilizar para recortar y redimensionar una vez subida la imagen es JCrop. De todos modos también dejo un ejemplo completo (último enlace), en el que puedes ver tranquilamente todo lo necesario.

    ResponderEliminar

Publicar un comentario