Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

18 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
lunes, 24 de mayo de 2010
¿Habéis observado que al generar una vista de edición tipada sobre una entidad que contiene propiedades decimales o de fecha, Visual Studio os genera una llamada extraña al helper TextBoxFor()?

Vamos a verlo en detalle. Partimos de una entidad del Modelo como la siguiente:

Entidad del Modelo
Generamos ahora el andamiaje con Visual Studio, utilizando la opción correspondiente del menú contextual, indicando que se trata de una vista de edición, sobre la entidad Persona:

Añadiendo una vista tipada de edición
El código generado por el entorno será el habitual. Sin embargo, si nos fijamos en el control de edición que Visual Studio ha generado para la propiedad FechaNacimiento, podremos ver que contiene un parámetro extraño:

TextBoxFor con propiedades de tipo fecha

Durante mucho tiempo no me ha llamado la atención probablemente por la costumbre de verlo en llamadas al helper TextBox(), donde el segundo parámetro se utilizaba para especificar el valor por defecto del <input>. Además, dado que la edición de fechas suele realizarse de forma algo más seria (en breve hablaremos de eso por aquí) y el código generado suele eliminarse, tampoco me había fijado demasiado.

Sin embargo, en cuanto observas un poco las distintas sobrecargas de TextBoxFor(), es fácil darse cuenta de que esta llamada no es correcta. De hecho, si observamos la porción del código fuente HTML generado relativa a la edición de esta propiedad, nos encontraremos con esto:

Etiqueta input mal generada
¿Ein? ¿Length=”15”? ¿Te suena esto a algo? Seguro que sí ;-)

Efectivamente, el atributo extraño de la etiqueta <input> no es más que la propiedad Length de la cadena que estamos pasando a TextBoxFor en su segundo parámetro, reservado en la  sobrecarga de TextBoxFor que encaja con la llamada para especificar los atributos HTML personalizados:

public static MvcHtmlString TextBoxFor<TModel, TProperty>(
    this HtmlHelper<TModel> htmlHelper,
    Expression<Func<TModel, TProperty>> expression,
    Object htmlAttributes
)

Curiosamente, el TextBoxFor() sólo es generado así en las vistas de edición (Edit), y sobre propiedades de tipo DateTime, decimal y double; en las vistas de creación (Create) no ocurre este problema, ni con otros tipos de datos.

Pero bueno, ya que tenemos el destornillador en la mano, cuesta muy poco trabajo dejar las cosas en su sitio ;-). Como vimos en otra ocasión, el código de las vistas que genera Visual Studio está basado en plantillas T4, y es realmente sencillo modificarlo, así que vamos a ello.

En primer lugar, acudimos a la carpeta del IDE donde se almacenan estas plantillas:
  • (IDE)\Common7\IDE\ItemTemplates\CSharp\Web\MVC 2\CodeTemplates\AddView
Siendo (IDE) el directorio raíz de Visual Studio, normalmente dentro de \Archivos de programa.

A continuación hacemos una copia de seguridad de la plantilla llamada Edit.tt (que más vale prevenir…), la abrimos con cualquier editor de texto y buscamos en su la cadena “TextBoxFor”, que encontraremos en una porción de código como la siguiente:

Porción de Edit.tt original
Ahora lo único que tenemos que hacer es eliminar la porción <#= property.Value #>, que es la que se encarga de emitir los atributos incorrectos. De hecho, sería simplemente dejar la línea tal y como ésta aparece en la plantilla de creación (Create.tt):

Porción de edit.tt modificada
Hecho esto, al generar de nuevo vistas de edición, ya las fechas y decimales aparecerán como deben:

Edición de una fecha con TextBoxFor, corregida
En fin, se trata de un pequeño detalle en las plantillas de edición que hacen que se genere un código incorrecto al cliente. Nada importante, pero sin duda una buena excusa para profundizar en los mecanismos de andamiaje del framework.

Publicado en: Variable not found.
domingo, 23 de mayo de 2010
Estos son los enlaces publicados en Variable not found en Facebook desde el domingo, 18 de abril de 2010 hasta el domingo, 23 de mayo de 2010. Espero que os resulten interesantes :-)
Y no olvides que, si te interesa, puedes seguir esta información en vivo y en directo desde Variable not found en Facebook, o a través de Twitter.
Publicado en: Variable not found
lunes, 17 de mayo de 2010
Desarrolladores tatuados ¡Ah, cómo nos parecemos los desarrolladores! Independientemente de la tecnología, el tipo de aplicación, o el cliente, parece que llevamos tatuados de serie unos comportamientos que repetimos una y otra vez ante escenarios similares.

Debe ser por eso que me divierten y me identifico tanto con los posts que enumeran estos hábitos tan comunes, como el clásico las “20 respuestas más utilizadas por los desarrolladores cuando sus programas no funcionan”, que pulula por la red desde hace muchos años y del que he encontrado versiones en todos los idiomas, por lo que soy absolutamente incapaz de citar la fuente exacta.

El hecho es que justo en el momento en que se nos informa de que algo ha fallado en una de nuestras aplicaciones, el cerebro comienza a trabajar aceleradamente en busca de una excusa explicación, por lo que he pensado que combinando esta lista de frases con unos sabios consejos, seguro que puedo ayudar a más de uno a evitar las tensiones derivadas de los errores en programación.

Y es que, al final, seguro que todos acabamos diciendo aproximadamente lo mismo…

20. “¡Qué raro!”. Primera regla del desarrollador experimentado: mostrar asombro cuando algo falla. Alguien que no se extrañe de la aparición de un error puede aparentar ser bastante mediocre. Hay quien incluso, para acentuar más aún su profesionalidad, añade “¡Imposible!”.

19. “Nunca había pasado antes”. Por si no queda claro con el conjunto de exclamaciones anteriores, apostillar con esta declaración deja claro que os encontráis ante algo insólito. Esto, además, puede ser útil para despertar el espíritu aventurero en tu interlocutor, y hacerlo partícipe en el enfrentamiento a lo desconocido que estáis a punto de comenzar.

18. “Ayer funcionaba”. Busca un soporte sólido para aferrarte, una referencia fiable como esa. Decir que ayer funcionaba es una afirmación ideal: difícil de probar, y si estás ante alguien cándido e inocente, será suficiente para convencerlo de que el error se ha generado hoy mismo, como por arte de magia.

17. “¿Cómo es posible?”. Otra oportunidad para demostrar tu asombro, que además surte mayor efecto si lo sigues con una explicación que tu interlocutor no sea capaz de entender: “cuando I,J,K y no M,N,L es imposible que X,Y,Z”.

16. “Debe ser un problema hardware”. Segunda regla del desarrollador experimentado: si la cosa se complica, puedes ir abriendo el camino a nuevas posibilidades, alejadas de nuestra responsabilidad. Nada podemos hacer contra fallos en la máquina, nosotros sólo entendemos de software. De hecho, si somos incapaces de dar con el problema (y por tanto incapaces de solucionarlo), echar las culpas al metal es una vía de salida de lo más airosa.

¡Te vas a tragar la pantalla! 15. “¿Qué hiciste mal para que fallara?”. Duda siempre de la lucidez del usuario. Si lo acusas de ser un zoquete tendrá que dedicar su tiempo de proceso a defender su postura, y no a vapulearte como te mereces.

14. “Hay algo raro en tus datos”. Es una variación más ligera del caso anterior; estás descargando responsabilidad sobre el usuario, aunque de forma menos agresiva, pues no es un ataque directo a su persona, sino a los datos que utiliza. Esta fórmula puede ser útil y prudente si, por ejemplo, el usuario del sistema es tu jefe.

13. “¡No he tocado ese módulo en semanas!”. Eso es, deja claro que tú no has tocado nada, y que por tanto la responsabilidad no es tuya. Si el módulo funcionaba hace un mes y no has tocado nada, está claro que la culpa el del usuario, del entorno, o del propio desgaste, como las ruedas de un coche.

12. “Debes estar utilizando una versión incorrecta”. Esta es una técnica avanzada para aplicar una vez te has percatado, en silencio claro está, de que había un error tuyo en tu aplicación. Acusa al usuario de utilizar una versión antigua del software, y suminístrale la versión correcta, en la que obviamente ya has parcheado el error.

11. “Se trata sólo de una casualidad”. Utiliza el azar como causa de los problemas; es impredecible, incontrolable y misterioso, una cortina de humo ideal para esconder nuestras meteduras de pata.

10. “Uno no puede probarlo todo”. No somos Dioses… quizás estemos muy cerca, pero no lo somos. Esta frase, a utilizar sólo en casos señalados, puede mostrar humildad y apelar a la bondad del interlocutor para disminuir la tensión. O también, puede servir para arremeter contra tu organización, la falta de recursos, de tiempo, etc., que el usuario empatizará y lamentará contigo.

9. “Eso no puede ser la causa del problema”. Sé tajante; aunque tenga razón, no permitas que un usuario listillo te revele el motivo exacto por el que ha fallado tu aplicación, pues nadie la conoce mejor que tú. Si después de usar esta frase confirmas que la causa era la apuntada por este individuo, no lo reconozcas, y utiliza verborrea técnica para desviar la atención a otras causas.

8. “Eso funciona, aunque no lo he probado”. Un desarrollador profesional no necesita probar las cosas para saber que funcionan, eso es cosa de débiles e inseguros. Una afirmación tan rotunda desequilibrará al interlocutor y lo hará dudar de su criterio a la hora de detectar el error, momento que puedes aprovechar para rematar con cualquier otra frase.

7. “Alguien debe haber cambiado mi código”. Otra regla de oro del desarrollador, aunque compartida con otras profesiones: échale las culpas a otro. Una vez demostrado que hay un fallo y que no puedes hacer responsable al usuario, no hay nada mejor que echar la culpa a los compañeros. Y si crees que no es ético, recuerda: ellos también lo harían.

6. “¿Has comprobado si tienes un virus?”. Los virus, esos seres misteriosos que sólo podemos comprender los iniciados y de los que todo el mundo ha oído historias terribles, son también firmes candidatos a salvarnos el trasero cuando la cosa se está poniendo peligrosa. Dado que la respuesta a esta pregunta casi siempre va a ser negativa, estamos ganando tiempo e introduciendo un factor externo sobre el que descargar las culpas, haciendo al usuario responsable por su incompetencia para mantener su equipo limpio.

BSOD 5. “Bueno, no funciona, pero mola, ¿eh?”. Ante una encerrona, apela al sentido del humor de tu interlocutor. Intenta detectar algún aspecto positivo del desastre, y preséntalo de forma atractiva para el usuario. ¿Un proceso tarda demasiado? Así tienes más tiempo para estirar los músculos; ¿La aplicación se cierra violentamente? Así puedes apagar antes el equipo e irte a casa; ¿Una BSOD? Fíjate qué fondo de pantalla azul eléctrico tan espectacular te pone el programa cuando falla.

4. “No puedes usar esa versión en tu sistema” Cuando el número de errores sea tan alto que pueda poner en peligro tu credibilidad como profesional, no reconozcas haberlos cometido; utiliza motivos estructurales como el hardware, los virus, o el versionado de sistemas para hacerles ver que eres totalmente inocente.

3. “¿Por qué quieres hacerlo así?” Una posibilidad muy socorrida es a veces instruir al usuario para que realice las tareas justo de la forma en que la aplicación funciona correctamente, censurándole rápida y tajantemente los intentos de salirse del camino marcado. A veces es más fácil adaptar el usuario a la aplicación que viceversa.

2. “¿Dónde estabas cuando el programa falló?” Ideal para aplicaciones que funcionan como procesos por lotes, o para intentar culpar al usuario de su ausencia en un momento vital como en el que se produjo el problema. Sobre todo si sabemos que el usuario es tendente a despistarse, esta puede ser un arma realmente interesante.

1. “No es un error, es que el programa funciona así”. Aprovecha la delgada línea que separa un bug de una feature. Esta técnica avanzada te permitirá desviar la atención hacia las personas que decidieron las funcionalidades, recogieron los requisitos, o validaron el software, pero nunca hacia el desarrollador que simplemente siguió las instrucciones.

¡En mi máquina funciona!Y por último, la frase más utilizada sin duda por los desarrolladores:

0. “¡En mi máquina funciona!” Desconcierta al interlocutor, y queda como debes, por encima de todos. El software funciona en entornos controlados como el tuyo, propios de un experto, donde todo está perfectamente en orden y lejos del alcance de las manazas del usuario final.

Eso sí, hay que estar preparados para una respuesta como la siguiente:
"¡No me importa si funciona en tu máquina!
¡No estamos vendiendo tu máquina!"
-- Vidiu Platon
Publicado en: Variable not found
domingo, 16 de mayo de 2010
Estos son los enlaces publicados en Variable not found en Facebook desde el domingo, 09 de mayo de 2010 hasta el domingo, 16 de mayo de 2010. Espero que os resulten interesantes :-)
Y no olvides que, si te interesa, puedes seguir esta información en vivo y en directo desde Variable not found en Facebook, o a través de Twitter.

Publicado en: Variable not found
martes, 11 de mayo de 2010
ASP.NET MVCUna de las mejoras más esperadas de ASP.NET MVC 2 es, sin duda, el sistema integrado de validación del Modelo basado en las anotaciones de datos (Data Annotations). Y aunque la implementación en general es bastante apañada, hay algunos aspectos mejorables, sobre todo cuando intentamos desarrollar aplicaciones en nuestro idioma.

Por ejemplo, existe un curioso comportamiento del juego de herramientas de validación en cliente y servidor en lo relativo a la introducción de decimales en el Modelo. Imaginemos la siguiente entidad de datos, con sus correspondientes anotaciones:

public class Producto
{
  [Required(ErrorMessage="*")]
  public string Nombre { get; set; }
 
  [DisplayName("Peso (Kg.)")]
  [Range(0.1, 10, ErrorMessage="Entre {1} y {2}")]
  [Required(ErrorMessage="*")]
  public double Peso { get; set; }
}

Centrándonos en la propiedad Peso, la intención de sus anotaciones está bastante clara: queremos que sea de introducción obligatoria, y que su valor se encuentre en el rango entre 0,1 y 10 kilogramos.

Creamos ahora un formulario de introducción de datos para dicha entidad utilizando como base el generado por defecto por Visual Studio. El código resumido de la vista es el siguiente:

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
 
<script src="/Scripts/MicrosoftAjax.js" type="text/javascript"></script>
<script src="/Scripts/MicrosoftMvcValidation.js" type="text/javascript"></script>   
 
<h2>Create</h2>
<% Html.EnableClientValidation(); %>
 
<% using (Html.BeginForm()) {%>
 
... // Omitido
 
<% } %>
 
</asp:Content>

Si utilizamos exclusivamente la validación en servidor no habrá problema, pero si, como en el código anterior, activamos en ella la validación en cliente, podemos encontrarnos con un curioso y problemático escenario debido a la falta de sincronización entre las culturas en cliente y en servidor.

Sintomatología

Si en el campo Peso introducimos un entero, por ejemplo un “2”, todo funciona correctamente. El validador en cliente dejará pasar el valor, y en servidor se realizará la conversión de dicho valor a double sin problema.

Sin embargo, al intentar introducir un valor no entero como “1,23” comienza la fiesta. Si utilizamos como separador de decimales una coma, el validador en cliente no nos permitirá hacer un submit del formulario, quedándonos atrapados en esta capa:

Problema en cliente al validar un rango double

Si en cambio utilizamos como separador un punto, por ejemplo “2.5”, la validación en cliente considerará que el valor decimal es correcto, y permitirá el envío de los datos del formulario. Sin embargo, el servidor intentará obtener el valor double utilizando el formato asociado a la cultura actual, en la que el punto no es un carácter válido de separación, por lo que decidirá que el estado del Modelo es inválido, y nos enviará de vuelta al formulario:

Problema en servidor al validar un rango double

(Por cierto, hace unas semanas comenté por aquí cómo modificar los mensajes de validación por defecto en ASP.NET MVC 2, como el error mostrado en la captura anterior).

En resumen: no podemos continuar si utilizamos como separador decimal la coma (primer caso), ni tampoco si utilizamos el punto (segundo caso). Literalmente, estamos en un callejón sin salida.

Diagnóstico

El problema se debe básicamente a que los scripts de validación están utilizando únicamente la cultura “en-US”, en la que el carácter de separación decimal es el punto. Es decir, por defecto se utilizan los formatos de fecha y numéricos de la cultura inglesa/americana, y no existe ningún punto en el código de las librerías de scripting MicrosoftAjax o MicrosoftMvcValidation donde se modifiquen estos parámetros.

Sin embargo, el tratamiento en servidor se realiza bajo la cultura definida en el hilo de ejecución actual, en este caso la correspondiente a “es-ES”, en la que las comas son los separadores entre la parte entera y la decimal.

Esto es fácil de comprobar introduciendo al final de la vista:

<p>
  Cultura en servidor:
  <%= System.Threading.Thread.CurrentThread.CurrentCulture.Name %>
</p>
 
<script type="text/javascript">
  alert("Cultura en cliente: " + Sys.CultureInfo.CurrentCulture.name); 
</script>
 La cultura en cliente y servidor son diferentes

Tratamiento

Es posible que haya formas más sencillas para solucionar esta cuestión, pero de momento la única que he encontrado para sincronizar las culturas es forzar en el lado cliente la utilización de las opciones culturales que estén siendo consideradas en servidor.

Para ello, necesitamos asignar durante la inicialización de la página un objeto de tipo CultureInfo a la propiedad Sys.CultureInfo.CurrentCulture. Este objeto contiene los formatos numéricos y de fecha que serán utilizados en las operaciones de conversión llevadas a cabo desde las librerías de scripting Microsoft Ajax, y necesita dicha información durante su instanciación, como en el siguiente ejemplo:

<script type="text/javascript">
  var ci = {
      "name": "es-ES",
      "numberFormat":   {información de formato numérico}, 
      "dateTimeFormat": {información de formato de fecha y hora}
  };
  Sys.CultureInfo.CurrentCulture = Sys.CultureInfo._parse(ci);
</script>

Sin embargo, esto es algo más complejo de lo que podría parecer en un principio. El formato numérico contiene información sobre los separadores de millares, decimales, formas de representar negativos, monedas, dígitos, etc; de la misma forma, respecto a las fechas, es necesario suministrar el formato, nombre de meses, días de la semana, etc. Sin duda, un trabajo demasiado duro para tener que hacerlo a mano.

Por suerte, la representación de los formatos de números y fechas utilizado por las librerías de scripting de Microsoft son idénticas en cliente y servidor, por lo que podemos serializar como JSON los objetos Thread.CurrentThread.CurrentCulture.NumberFormat y Thread.CurrentThread.CurrentCulture.DateTimeFormat y utilizar el resultado para crear el objeto CultureInfo:

<%
  JavaScriptSerializer jss = new JavaScriptSerializer();
  var cultureInfo = Thread.CurrentThread.CurrentCulture;
  string name = cultureInfo.Name;
  string numberFormat = jss.Serialize(cultureInfo.NumberFormat);
  string dateFormat = jss.Serialize(cultureInfo.DateTimeFormat); 
%>
<script type="text/javascript">
  var ci = {
      "name": "<%= name %>",
      "numberFormat":  <%= numberFormat %>,
      "dateTimeFormat": <%= dateFormat %>
  };    
  Sys.CultureInfo.CurrentCulture = Sys.CultureInfo._parse(ci);
</script>

Pero está claro que no tiene demasiado sentido repetir el código anterior a lo largo y ancho de las vistas de edición de datos donde queramos activar la localización. Seguro que podemos mejorar esto… ;-)

Medicina genérica: El helper Html.EnableLocalizedClientValidation()

Vamos a crear un helper que realice por nosotros estas tareas de la forma más sencilla posible.

Html.EnableLocalizedClientValidation() encapsula el comportamiento del helper estándar EnableClientValidation(), y añade los scripts de inicialización de las opciones culturales comentados anteriormente. Su uso será como se muestra a continuación:

<script src="../../Scripts/MicrosoftAjax.js" type="text/javascript"></script>
<script src="../../Scripts/MicrosoftMvcValidation.js" type="text/javascript"></script> 
<%= Html.EnableLocalizedClientValidation() %>
<h2>Create</h2>
<% using (Html.BeginForm()) { %>
// .. El resto del código de vista

La única diferencia respecto al método habitual es que estamos utilizando una expresión de salida <%= %>, y que el helper invocado es EnableLocalizedClientValidation().

El código del helper es:

using System.Text;
using System.Web.Script.Serialization;
using System.Threading;
 
namespace System.Web.Mvc
{
  public static class Extensions
  {
    public static MvcHtmlString EnableLocalizedClientValidation(this HtmlHelper html)
    {
      // Habilitamos la validación en cliente
      html.EnableClientValidation();
 
      // Obtenemos la información de la cultura actual
      JavaScriptSerializer jss = new JavaScriptSerializer();
      var cultureInfo = Thread.CurrentThread.CurrentCulture;
      string name = cultureInfo.Name;
      string numberFormat = jss.Serialize(cultureInfo.NumberFormat);
      string dateFormat = jss.Serialize(cultureInfo.DateTimeFormat);
 
      // Generamos el código
      StringBuilder sb = new StringBuilder();
      sb.Append("<script type=\"text/javascript\">");
      sb.AppendLine("var ci = {");
      sb.AppendLine("    \"name\": \"" + name + "\",");
      sb.AppendLine("    \"numberFormat\": " + numberFormat + ", ");
      sb.AppendLine("    \"dateTimeFormat\": " + dateFormat);
      sb.AppendLine("};");
      sb.AppendLine("Sys.CultureInfo.CurrentCulture = Sys.CultureInfo._parse(ci);");
      sb.AppendLine("</script>");
      return MvcHtmlString.Create(sb.ToString());
    }
  }
}

Puedes descargar el código fuente desde mi SkyDrive.

Una última anotación: terminando de escribir este post, observo que el gran Phil Haack (PM de ASP.NET MVC) acaba de publicar una entrada comentando el mismo tema, y enfocando la solución desde otra óptica. Una lástima que no lo haya publicado antes, me habría ahorrado el tiempo que me ha llevado esta investigación, aunque, en cualquier caso, ha valido la pena para entender un poco más los entresijos del framework.

Publicado en: Variable not found
Hey, ¡estoy en twitter!