Limpiar un input type=file

No es algo excesivamente frecuente, pero en ocasiones podemos necesitar limpiar el valor de un campo de tipo file (el que usamos para hacer los uploads) de un formulario, por ejemplo, para evitar que el usuario envíe un archivo que por cualquier motivo no deba ser subido al servidor.

O dicho de otra forma, imaginemos la siguiente porción de un formulario en pantalla, que podría ser generada con el código que podéis ver justo a continuación:
Campo de envío de archivos

<label for="archivo">Archivo a enviar:</label>
<input type="file" id="archivo" name="archivo" />
<input type="button" onclick="limpiarInputFile('archivo');" value="Limpiar" />

Y la pregunta en este momento sería, ¿qué código deberíamos implementar en la función limpiarInputFile() que estamos utilizando en el evento onclick si quisiéramos limpiar o inicializar el contenido del campo archivo?

Aunque de forma intuitiva podría parecer que basta con establecer el valor del campo a una cadena vacía, una prueba rápida nos demostrará que esto no es posible. Desde hace ya tiempo, por motivos de seguridad, los navegadores no permiten el acceso de escritura a la propiedad value en los campos de envío de archivos, por lo que nos encontramos una vía sin salida. Esto lo vemos con el siguiente código, utilizando jQuery:

    function limpiarInputfile(id) {
        var input = $('#' + id);
        var nuevoValor = "c:\windows\system32\mspaint.exe";
        
        alert(input.val());       // Muestra "C:Datos.dat"
        input.val(nuevoValor);    // Establecemos un nuevo valor
        alert(input.val());       // ¡¡Muestra "C:Datos.dat"!!
    }

Como vemos, somos vilmente ignorados cuando intentamos establecerle un valor.

Pues bien, una posible solución consiste en eliminar del DOM el elemento <input type="file"> y volver a crearlo justo después en el mismo lugar. He visto por ahí varias implementaciones que obligaban a introducir este elemento dentro de un contenedor, pero he creado otra que creo que es más sencilla e igual de efectiva:

    function limpiarInputfile(id) {
        var input = $('#' + id);
        var clon = input.clone();  // Creamos un clon del elemento original
        input.replaceWith(clon);   // Y sustituimos el original por el clon
    }

Y eso es todo :-). Observad que lo único que hacemos es crean un clon del elemento original cuyo value por supuesto estará en blanco (recordad que esta propiedad no se puede establecer), y justo a continuación eliminamos el elemento original sustituyéndolo por este clon.

Si queremos generalizar este código e implementar esta funcionalidad de forma no intrusiva podríamos hacer lo siguiente:

    $(function () {
        $("input[type=file]").after(
            "<input type='button' class='limpiar-inputfile' value='Limpiar'>"
        );
        $(".limpiar-inputfile").click(function () {
            var input = $(this).prev("input[type=file]");
            var clon = input.clone();
            input.replaceWith(clon);
            return false;
        });
    });

Este código añade automáticamente un botón “Limpiar” a continuación de todos los <input type=file> de la página, implementando en el manejador del evento click la lógica de inicialización del componente que hemos visto antes. De esta forma, sólo se introducirá en la página el botón de limpiado cuando estén activados los scripts, que es en el único momento en que su ejecución tendrá sentido con la solución propuesta.

Espero que os sea de utilidad.

Publicado en: Variable not found.

El Tao de la Programación

The Tao of ProgrammingHace unos días charlaba con un colega sobre aspectos filosóficos del desarrollo de software, y me pareció curioso que no conociera “The Tao of Programming”, todo un clásico de la literatura friki, y lectura indispensable para los que nos dedicamos a esto.

Este libro, escrito en el año 1987 por Geoffrey James, recoge, en un humorístico tono de misticismo oriental, distintas historias y parábolas con geniales mensajes de fondo sobre calidad en la programación, gestión de proyectos y otros temas que siguen siendo válidos veinticinco años después.

Como probablemente habrá más lectores que no lo conozcan, me ha parecido interesante reproducirlo aquí traducido en su totalidad, por supuesto contando previamente con el permiso expreso del Sr. James.

¡Que aproveche!

Validator providers en ASP.NET MVC

ASP.NET MVCEn la pasada charla sobre el sistema de validaciones de MVC 3 vimos un ejemplo, creo que bastante ilustrativo, de los proveedores de validación del framework. Concretamente, implementamos un proveedor capaz de obtener las anotaciones partiendo de las restricciones definidas en el web.config. Es decir, las reglas de comprobación como Required o StringLength no las definíamos a nivel de código mediante atributos, sino en el archivo de configuración, lo que podía aportar interesantes ventajas vistas a flexibilizar nuestras soluciones.

En este post vamos a ver un nuevo ejemplo de cómo utilizar el mismo mecanismo de proveedores, pero esta vez para conseguir de forma muy sencilla simplificar el código de clases del modelo en las que todas sus propiedades sean por defecto obligatorias, salvo aquellas en las que indiquemos expresamente lo contrario.

1. Empezando por el final…

Lo que pretendemos hacer es crear dos nuevos atributos, AllRequired y NotRequired. El primero, aplicado a una clase indicará la obligatoriedad de todas sus propiedades, mientras que el segundo, aplicado a una propiedad, indicará que ésta es opcional.

Observad en la siguiente tabla el efecto que podríamos conseguir sobre el código:

Enfoque tradicional Usando AllRequired y NotRequired
public class Friend
{
    [Required] 
    public string Name { get; set; }
    [Required]
    public string Surname { get; set; }
    [Required]
    public string Nickname { get; set; }
    [Required]
    public string Address { get; set; }
    [Required]
    public string City { get; set; }
    [Required]
    public string Country { get; set; }
    public string Phone { get; set; }
    [Required]
    public string MobilePhone { get; set; }
    [Required]
    public string Email { get; set; }
}
[AllRequired]
public class Friend
{
    public string Name { get; set; }
    public string Surname { get; set; }
    public string Nickname { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string Country { get; set; }
    [NotRequired]
    public string Phone { get; set; }
    public string MobilePhone { get; set; }
    public string Email { get; set; }
}

2. Model validator providers

Cuando el framework necesita obtener los validadores asociados a una clase y sus propiedades, no lo hace de forma directa sino a través de unos componentes llamados model validator providers. Éstos son los encargados de acceder a las distintas fuentes en las que puede existir información sobre las reglas de validación para cada clase.

Así, existe “de serie” un model validator provider específico para obtener los validadores partiendo de las anotaciones realizadas sobre la clase (DataAnnotationsModelValidatorProvider ), que es el método utilizado normalmente; existe otro para obtener las implementaciones del interfaz IDataErrorInfo (DataErrorInfoModelValidatorProvider), y otro que se encarga de validaciones implícitas del tipo (ClientDataTypeModelValidatorProvider).

Todos ellos se encuentran registrados en la colección pública ModelValidatorProviders.Providers, de forma que cuando el framework necesita obtener los validadores asociados a una entidad, lo que hace es recorrer dicha colección e ir llamando uno por uno a los proveedores solicitándoles información de validación para cada una sus propiedades.

¡Todo encaja!Este proceso se realiza, cuando se tiene activa la validación en cliente, para obtener las reglas y parámetros que hay que llevar a la vista, o también durante el binding, para obtener los validadores, ejecutarlos y actualizar el ModelState con el resultado.

Pero sin duda lo mejor que tiene este sistema es su facilidad de extensión. Podemos añadir con suma facilidad nuevos proveedores a la colección ModelValidatorProviders a la que hacía referencia anteriormente, de forma que podemos introducir nuestra propia lógica de generación de validadores en el flujo de ejecución de peticiones del framework.

Y es lo que vamos a hacer a continuación: implementaremos nuestro proveedor para generar automáticamente validaciones de tipo Required para todas las propiedades de las clases decoradas con el atributo [AllRequired], exceptuando aquellas propiedades en las que se haya indicado lo contrario con [NotRequired].

3. Los atributos AllRequired y NotRequired

Antes que nada, necesitamos definir estos dos atributos para poder continuar. El código es tan simple como el que veis a continuación:

[AttributeUsage(AttributeTargets.Class)]
public class AllRequiredAttribute : Attribute
{
        
}
 
[AttributeUsage(AttributeTargets.Property)]
public class NotRequiredAttribute: Attribute
{
        
}

Observad que no es necesario que éstos hereden de ValidationAttribute; de hecho, no estamos creando anotaciones personalizadas, sólo atributos que nos permitan “marcar” en clases y propiedades para generar más adelante, desde el proveedor, las anotaciones necesarias.

Los [AttributeUsage] lo único que hacen es restringir su uso de forma que [AllRequired] se pueda utilizar únicamente sobre clases, mientras que [NotRequired] sea utilizable exclusivamente en propiedades, más que nada para que se comprueben en compilación estos aspectos.

4. Implementando nuestro model validator provider

La implementación de un model validator provider es bastante sencilla. Lo único que tenemos que hacer es heredar de ModelValidatorProvider e implementar su método GetValidators(), que es el que invocará el framework para obtener los validadores de cada una de las propiedades de la clase.

En nuestro caso, simplemente comprobaremos que la clase esté marcada como [AllRequired] , retornando un validador de tipo Required para todas aquellas propiedades que no se hayan marcado con [NotRequired]. Casi mejor lo vemos sobre el código:

public class AllRequiredValidatorProvider : ModelValidatorProvider
{
  public override IEnumerable<ModelValidator> GetValidators(
      ModelMetadata metadata, ControllerContext context)
  {
      var allRequiredAttribute =
              TypeDescriptor.GetAttributes(metadata.ContainerType)
              .OfType<AllRequiredAttribute>().FirstOrDefault();
 
      if (allRequiredAttribute != null)
      {
        var property = metadata.ContainerType.GetProperty(metadata.PropertyName);
        var isOptional = property.GetCustomAttributes(typeof(NotRequiredAttribute), false).Any();
        if (!isOptional)
        {
          var requiredValidator = new RequiredAttribute() { ErrorMessage = "Requerido" };
          yield return new RequiredAttributeAdapter(metadata, context, requiredValidator);
        }
      }
  }
}

Observad la firma del método: retorna una colección de objetos que heredan de ModelValidator, que son adaptadores de los distintos mecanismos de validación que podemos emplear en MVC. En la práctica permiten homogeneizar la diversidad de validadores vistas al framework, y a veces incluyen cierta lógica, como la generación de las reglas de validación en cliente.

El framework incluye ya adaptadores para cada una de las anotaciones habituales (RequiredAttributeAdapter, StringLengthAdapter, RegularExpressionAttributeAdapter …), que podemos instanciar directamente para ir construyendo la colección que retorna el método GetValidators(). En el código anterior, fijaos que en la última línea retornamos, usando yield, un nuevo adaptador de tipo RequiredAttributeAdapter, a cuyo constructor suministramos una instancia del atributo Required convenientemente inicializada.

Un último detalle: tened en cuenta que por simplificar el ejemplo no he prestado atención a aspectos como el rendimiento, que podría mejorarse introduciendo una caché, o la personalización de los mensajes de error, que sería fácilmente implementable permitiendo parámetros en el atributo [AllRequired] y suministrándolos más adelante en la instanciación de los RequiredAttribute que retornamos desde el proveedor, como hacemos ahora con la cadena de texto constante “Requerido”.

5. Registro del proveedor

Por último, ya sólo queda indicar al framework que debe utilizar nuestro proveedor para obtener los validadores, por lo que lo añadimos en el global.asax a la colección de providers:

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
 
    ModelValidatorProviders.Providers.Add(new AllRequiredValidatorProvider());
 
    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);
}

Casi todo obligatorio¡Y ahí lo tenemos! A partir de este momento, ya podemos hacer uso de estos nuevos atributos en nuestras clases del modelo o view models, como se muestra a continuación:

[AllRequired]
public class Friend
{
    public string Name { get; set; }
    public string Surname { get; set; }
    public string Nickname { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string Country { get; set; }
    [NotRequired]
    public string Phone { get; set; }
    public string MobilePhone { get; set; }
    public string Email { get; set; }
}

En tiempo de ejecución tendremos, por supuesto, validación tanto en cliente como en servidor; de hecho, el resultado será idéntico al conseguido si hubiéramos decorado cada una de las propiedades anteriores con él atributo Required, puesto que en realidad estamos inyectándolo en todas las propiedades de forma automática, exceptuando en aquellas donde se ha indicado expresamente lo contrario.

Podéis descargar el proyecto de prueba desde Skydrive (VS2010+MVC3).

Publicación original (8-nov-2011): http://www.variablenotfound.com/2011/11/validator-providers-en-aspnet-mvc.html
Tip: ¿todavía no conoces la selección de enlaces interesantes de Variable not found?

Compactación y minimización de Javascript y CSS para ASP.NET

ASP.NET MVCHabía comentado que una de las novedades que ya podíamos disfrutar en la developer preview de MVC 4 era el sistema de compactación y minimización de scripts y CSS, y que, además de ser bastante útil y sencillo, podíamos utilizarlo a día de hoy en nuestras aplicaciones MVC 3 e incluso en ASP.NET Webforms.

Este mecanismo hace posible la creación de bundles, o paquetes de uno o varios archivos de scripts o estilos, que son optimizados en tamaño por el servidor y cargados desde las páginas en una única petición. El proceso de compactación se realizaría en servidor, pero sólo la primera vez, quedando almacenado en caché para las peticiones posteriores.

Por ejemplo, en el caso de los scripts, en lugar de tener en nuestras vistas o layouts referencias hacia decenas de archivos “.js” (jQuery, UI, validadores, plugins, etc.), simplemente incluiríamos la carga de un archivo, que sería generado por el servidor mediante el empaquetado y la compactación de todos ellos.

Y con las hojas de estilo CSS ocurriría lo mismo: podríamos cargar desde el cliente un único archivo .css, que contendría la suma compactada de todos los necesarios para el funcionamiento de la aplicación.

< disclaimer>

Todo lo que voy a contar a continuación está probado con la versión previa para desarrolladores de MVC. El ensamblado Microsoft.Web.Optimization es la versión 0.1, y de él puede cambiar hasta el nombre 😉

< /disclaimer>

Para empezar, el paquete Microsoft.Web.Optimization se incluye instalado de serie en MVC 4 (a día de hoy, hay que decir). En cambio, en ASP.NET MVC 3 o Webforms debemos descargarlo utilizando Nuget:

PM> Install-Package Microsoft.Web.Optimization
Successfully installed 'Microsoft.Web.Optimization 0.1'.
Successfully added 'Microsoft.Web.Optimization 0.1' to Mvc4Application49.

Una vez instalado, ya casi tenemos el trabajo hecho 😉

La forma rápida

Si lo que queremos es probar rápidamente el sistema de empaquetado, basta con añadir el siguiente código en la inicialización de nuestra aplicación:

   protected void Application_Start()
   {
       ... 
       BundleTable.Bundles.EnableDefaultBundles();
       ...

¡Y esto es todo! A partir de este momento, podemos acceder a las URL:

  • /scripts/js, que nos retornará un único archivo script conteniendo todos los scripts disponibles en la carpeta /scripts del proyecto (sin incluir subdirectorios), minimizados y compactados.
  • /content/css, que de la misma forma, retornará un único archivo .css conteniendo la suma compactada de todos los css que el sistema encuentre en la carpeta /content.

Vale, he simplificado un poco para que pudierais comprender rápidamente el ejemplo, pero en realidad es aún más potente:

  • Cualquier petición del estilo “{path}/js” hará que el sistema retorne en un único archivo compactado todos los scripts disponibles en la carpeta {path}.
  • De la misma forma, todas las peticiones a “{path}/css” retornará un único archivo .css con la suma compactada de todas las hojas de estilo presentes en la carpeta {path}.
  • Se eliminan las versiones de documentación (*-vsdoc.js, *.debug.js…), es decir, serán ignoradas todas ellas a la hora de generar el archivo compactado único.
  • Se tiene en cuenta la ordenación, sobre todo a la hora de anexar los scripts en un único archivo. De esta forma, scripts como jQuery se carguen antes que sus plugins, y otros habituales en el mundo CSS como reset.css o normalize.css sean cargados al final del bundle de estilos.

Si queremos hacer utilizar estos archivos minimizados en nuestro sitio web, simplemente tendremos que acudir al layout (o master) y modificar los enlaces hacia estas direcciones. Observad que tendréis que poner un enlace por cada carpeta:

   <link href="/Content/css" rel="stylesheet" type="text/css" />
   <script src="/Scripts/js" type="text/javascript"></script>

Ojo, muy Importante: para que todo funcione bien los scripts deben ser sintácticamente correctos, de lo contrario podemos encontrarnos con que la versión compactada nos dé fallos en tiempo de ejecución. Esto es así porque el proceso de compactación no sólo elimina espacios sobrantes, sino que acorta los nombres de variables y funciones privadas.

De hecho, a día de hoy este sistema no funciona bien con las bibliotecas que se encuentran por defecto en la carpeta /scripts de los proyectos MVC 3, es necesario actualizarlos (usando Nuget son sólo unos segundos). Con los scripts incluidos en MVC 4 no hay problema.

También los .css deben ser correctos, aunque en caso contrario lo único que ocurrirá es que se retornará un único archivo sin compactar.

¡Quiero más control!

El sistema de bundling es realmente potente y flexible. En el ejemplo anterior se trataba de poner en marcha este sistema de forma rápida, pero se puede conseguir un control mucho más exhaustivo sobre lo que se compacta y lo que no, y sobre la forma de acceder a estos recursos.

La siguiente porción de código muestra cómo podríamos inicializar la tabla de bundles de forma más detallada:

   Bundle cssBundle = new Bundle("~/estilo.css", typeof(CssMinify));
   cssBundle.AddDirectory("/content", "*.css", searchSubdirectories: true);
   BundleTable.Bundles.Add(cssBundle);

   Bundle jsBundle = new Bundle("~/scripts.js", typeof (JsMinify));
   jsBundle.AddFile("~/scripts/jquery-1.6.4.js");
   jsBundle.AddFile("~/scripts/modernizr-2.0.6.js");
   jsBundle.AddDirectory("~/scripts/site", "*.js", searchSubdirectories: false);
   BundleTable.Bundles.Add(jsBundle);

Como podemos observar, en primer lugar se crea un bundle de tipo CSS, en el que se añaden todos los archivos con el patrón *.css que se encuentren en la carpeta /content y todos sus subdirectorios. Este paquete compactado sería accesible a través de la URL “/estilo.css”.

En el segundo bloque creamos un bundle en la URL “/script.js” al que añadimos algunos archivos de forma manual, y todos los .js que encontremos en la carpeta /scripts/site, sin incluir subdirectorios.

¿Fácil, verdad?

En conclusión, aunque todavía tendrá que cambiar, en este momento ya podemos ver una herramienta muy potente y sencilla de utilizar, que seguro entrará a formar parte de nuestros desarrollos en breve. Ya el colmo sería que los contenidos se comprimieran en gzip, pero por la pinta que tienen los componentes probablemente sea una funcionalidad a la que podamos aspirar en breve 🙂

Publicación original (18-oct-2011): http://www.variablenotfound.com/2011/10/compactacion-y-minimizacion-de.html

Tip: ¿todavía no conoces la selección de enlaces interesantes de Variable not found?