Gerardo Contijoch

Experiencias del día a día trabajando con .NET – ASP.NET, C#, ASP.NET MVC y demas…

Archive for the ‘Desarrollo Web’ Category

Un CheckBoxList que funciona en ASP.NET MVC

Posted by Gerardo Contijoch en julio 4, 2009

Si buscamos en internet ‘CheckBoxList ASP.NET MVC’ encontraremos infinidad de páginas con quejas de la desaparición del método de extensión CheckBoxList() de la clase HtmlHelper en la Preview 5 de ASP.NET MVC (antes de la versión oficial). Este es un problema de aparente fácil resolución ya que el método CheckBox() sigue estando presente, por lo que una lista de CheckBoxes podría crearse con un código similar al siguiente:

   1: <% for (int i = 0; i < 4; i++) {%>
   2:   <%= Html.CheckBox("checks", (object)new { value = "val" + i.ToString() })%><%= "Check " + i.ToString() %><br/>
   3: <%}%>

Sin embargo, rápidamente nos vamos a encontrar con dos problemas. El primero de ellos se hace presente dentro del código de la acción que se ejecuta al postear el form.

Dada la siguiente vista:

   1: <% using (Html.BeginForm("Index1", "Home", FormMethod.Post)) { %>
   2:     <% for (int i = 0; i < 4; i++) {%>
   3:         <%= Html.CheckBox("checks1", (object)new { value = "val" + i.ToString() })%><%= "Check " + i.ToString() %><br/>
   4:     <%}%>
   5:     <input type="submit" value="Post!" />
   6: <%}%>

veamos como recibimos los valores de los CheckBoxes si tildamos sólo los dos primeros CheckBoxes (los asociados a los valores ‘val0’ y ‘val1’):

aspnetmvc-checkboxes-valoresChecks1

Como se puede apreciar, lo que recibimos es un array de valores un tanto desconcertante. Esto se debe a la manera en que postea el valor de los CheckBoxes.

Posteo de Checkboxes

Un CheckBox no es más que un input de tipo checkbox en una página, el cual puede tener o no un valor asociado. A diferencia de como funciona un input de tipo text (un TextBox), el valor del checkbox sólo se postea si el mismo esta tildado. Esto significa que si en un form HTML no tildamos un CheckBox, su valor no será enviado al servidor, lo cual puede traer muchos problemas en ASP.NET MVC si nuestra acción (la asociada al form que posteamos) tiene que recibir como parámetro el valor del CheckBox, ya que la llamada fallará cuando este no este tildado y el DefaultModelBinder no pueda asignarle un valor al parámetro asociado al mismo (debido a que no se posteó).

Es por este problema que el método de extensión CheckBox renderiza dos inputs en vez de uno solo. El primer input es el propio CheckBox (un imput de tipo ‘checkbox’), y el segundo es un input de tipo hidden llamado igual, pero cuyo valor esta hardcodeado a ‘false’. Es decir, dado el siguiente código:

   1: <%= Html.CheckBox("checkbox") %>"Check"

se renderiza lo siguiente en la página:

   1: <input id="checkbox" type="checkbox" value="true" name="checkbox"/>
   2: <input type="hidden" value="false" name="checkbox"/>
   3: Check
   4: <br/>

Así, si posteamos el form sin tildar el CheckBox, se postea el valor del input oculto, es decir, para la variable del post ‘checkbox’ se asigna el valor ‘false’ (que en el servidor es transformado en el booleano False). En cambio, si tildamos el CheckBox, se postea el valor ‘true, false’ (uno por cada input). Este valor es procesado por el DefaultModelBinder y lo interpreta como el booleano True.

Posteo de más de un CheckBox

Hay que reconocer que es un método bastante ingenioso de resolver el problema, pero lamentablemente está pensado para funcionar sólo con un único CheckBox. Volviendo al ejemplo que presenté al comienzo de este post, se puede ver ahora que el array que recibimos en nuestra acción esta conformado por los valores asociados a cada uno de los CheckBoxes renderizados, esto es, se posteo ‘val0, false’ para el primer CheckBox, ‘val1, false’ para el segundo, ‘false’ para el tercero, y ‘false’ para el cuarto (recordemos que los dos últimos CheckBoxes no fueron tildados). Esta forma de recibir los parámetro puede ser bastante problemática. Por un lado, no podemos procesar los valores con un foreach ya que no todos los valores nos interesan, sólo aquellos que son distintos a ‘false’ nos resultan útiles. Uno podría discriminar estos valores para recuperar sólo el set que nos interesa, pero esto es posible únicamente si no posteamos valores booleanos ya que si efectivamente queremos un valor ‘false’ cuando no tildamos un CheckBox, no vamos a poder ubicarlo fácilmente dentro del array. Es verdad que uno puede imaginarse una lógica un tanto compleja en donde si se encuentra un valor ‘true’, entonces ignora el siguiente valor (que debería ser ‘false’), pero esto tiene una utilidad limitada, ya que muy posiblemente nos topemos con el segundo de los problemas que mencioné anteriormente.

Este segundo problema es similar al del posteo de los valores, pero se presenta a la hora de mostrarlos a los valores. Cuando posteamos un form en ASP.NET MVC los parámetros de las acciones se cargan en el ModelState, lo cual nos permite, al momento de renderizar la vista inicializar los valores de los controles con los valores posteados para que los mismos no aparezcan vacíos (recordemos que la web es stateless y luego de un post las páginas se vuelven a cargar desde cero). El modo de hacerlo es matcheando los nombres de los controles (atributo name) con las entradas del ModelState. Así, si tenemos un TextBox llamado ‘txtNombre’, ASP.NET MVC va a crear una entrada dentro del ModelState con el nombre ‘txtNombre’ asociado al valor posteado en el TextBox. Al momento de renderizar la página se consulta el ModelState y se cargan los valores posteados con anterioridad. Particularmente en nuestro caso, lo que se carga en el ModelState es un arrays de valores, el cual esta asociado a todos los CheckBoxes en el form. Y eso es un problema ya el método CheckBox() no esta preparado para procesar un array de valores, sino un único valor que determina si un CheckBox esta o no tildado (ese código puede verse claramente cerca de la línea 153 de la clase InputExtensions en el código fuente de ASP.NET MVC). Como consecuencia de esto, si tenemos más de un CheckBox con el mismo nombre en el form, los mismos no van a recuperar su estado anterior.

Un CheckBoxList que funciona

Dado ese problema, decidí crear un nuevo CheckBoxList personalizado (hay un par de implementaciones dando vueltas, pero no encontré ninguna que resolviera el segundo problema).

El código es bastante sencillo y esta ‘inspirado’ en el código del CheckBox original de ASP.NET MVC.

   1: public static partial class HtmlHelperExtensions {
   2:
   3:     public static string CheckBoxList(this HtmlHelper htmlHelper, string name, IEnumerable<string> values, object htmlAttributes) {
   4:         return CheckBoxList(htmlHelper, name, values, values, htmlAttributes);
   5:     }
   6:
   7:     public static string CheckBoxList(this HtmlHelper htmlHelper, string name, IEnumerable<string> values, IEnumerable<string> labels, object htmlAttributes) {
   8:         // No creamos ningun CheckBox si no hay valores
   9:         if (values == null) {
  10:             return "";
  11:         }
  12:
  13:         if (labels == null) {
  14:             labels = new List<string>();
  15:         }
  16:
  17:         RouteValueDictionary attributes = htmlAttributes == null ? new RouteValueDictionary() : new RouteValueDictionary(htmlAttributes);
  18:         attributes.Remove("checked");
  19:
  20:         StringBuilder sb = new StringBuilder();
  21:
  22:         string[] modelValues = new string[] { };
  23:
  24:         ModelState modelState;
  25:         if (htmlHelper.ViewData.ModelState.TryGetValue(name, out modelState)) {
  26:             modelValues = ((string[])modelState.Value.RawValue);
  27:         }
  28:
  29:         // Por cada valor pasado generamos un CheckBox
  30:
  31:         IEnumerator<string> labelEnumerator = labels.GetEnumerator();
  32:         foreach (string s in values) {
  33:             // Si el array contiene el valor correspondiente a este checkbox, entonces fue chequeado
  34:             bool isChecked = modelValues.Contains(s);
  35:             sb.Append(CrearCheckBox(name, s, isChecked, attributes));
  36:
  37:             labelEnumerator.MoveNext();
  38:             if (labelEnumerator.Current != null) {
  39:                 sb.AppendLine(labelEnumerator.Current);
  40:             }
  41:         }
  42:
  43:         // Creamos el div contenedor
  44:         TagBuilder divTag = new TagBuilder("div");
  45:         divTag.InnerHtml = sb.ToString();
  46:
  47:         // No nos olvidemos de indicar si hay un error en alguno de los checks
  48:         if (modelState != null && modelState.Errors.Count > 0) {
  49:             divTag.AddCssClass(HtmlHelper.ValidationInputCssClassName);
  50:         }
  51:
  52:         return divTag.ToString(TagRenderMode.Normal);
  53:     }
  54:
  55:     private static string CrearCheckBox(string name, string value, bool isChecked, IDictionary<string, object> htmlAttributes) {
  56:         TagBuilder tagBuilder = new TagBuilder("input");
  57:         tagBuilder.MergeAttributes(htmlAttributes);
  58:         tagBuilder.MergeAttribute("type", "checkbox");
  59:         tagBuilder.MergeAttribute("name", name, true);
  60:
  61:         tagBuilder.GenerateId(name);
  62:
  63:         if (isChecked) {
  64:             tagBuilder.MergeAttribute("checked", "checked");
  65:         }
  66:
  67:         if (value != null) {
  68:             tagBuilder.MergeAttribute("value", value, true);
  69:         }
  70:
  71:         return tagBuilder.ToString(TagRenderMode.SelfClosing);
  72:     }
  73: }

Su uso es muy sencillo:

   1: <%
   2:    List<string> values = new List<string>() { "val0", "val1", "val2", "val3", "val4" };
   3:    List<string> labels = new List<string>() { "Check 0", "Check 1", "Check 2", "Check 3", "Check 4" };
   4: %>
   5: <%= Html.CheckBoxList("checks3", values, labels, null) %>

Los valores en la acción son recibidos así:

aspnetmvc-checkboxes-valoresChecks2

El código podría mejorarse un poco más agregando la posibilidad de aplicarles estilos al div contenedor de los CheckBoxes, usar labels reales en vez de solo texto, agregar sobrecargas a los métodos, etc., pero preferí dejarlo así porque era más sencillo. Es preciso aclarar que este CheckBoxList, al igual que la primer alternativa que presenté, no va a funcionar bien con valores booleanos ya que la idea es que los valores asociados a cada uno de los CheckBoxes sean únicos.

Si encuentran algún bug o tienen algo que aportar, no duden en comentarlo.

¡Nos vemos en el próximo post!

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Anuncios

Posted in ASP.NET MVC, Desarrollo Web | Etiquetado: , , , | 3 Comments »

Investigando los eventos de la clase HttpApplication en ASP.NET

Posted by Gerardo Contijoch en junio 27, 2009

Hace unos días me encontré con este interesantísimo post de Rick Strahl en donde responde a una pregunta que alguien le hizo. La pregunta en cuestión es ¿Cómo es que los handlers de los eventos Application_ en ASP.NET son enlazados para que sean invocados automáticamente?

Es una pregunta interesante ya que normalmente uno enlaza a mano los eventos que desea capturar, pero en el caso de los eventos de la clase HttpApplication, la situación es diferente porque no podemos enlazarlos nosotros mismos (en realidad si podemos, pero no tiene ningún efecto). Veamos como es el asunto.

Cuando agregamos un archivo Global.asax a nuestro proyecto vemos que la clase Global ya posee algunos handlers creados:

   1: public class Global : System.Web.HttpApplication {
   2:
   3:     protected void Application_Start(object sender, EventArgs e) { }
   4:
   5:     protected void Session_Start(object sender, EventArgs e) { }
   6:
   7:     protected void Application_BeginRequest(object sender, EventArgs e) { }
   8:
   9:     protected void Application_AuthenticateRequest(object sender, EventArgs e) { }
  10:
  11:     protected void Application_Error(object sender, EventArgs e) { }
  12:
  13:     protected void Session_End(object sender, EventArgs e) { }
  14:
  15:     protected void Application_End(object sender, EventArgs e) { }
  16: }

Lógicamente, al handler Application_Start nunca vamos a poder enlazarlo nosotros mismos ya que el mismo es ejecutado cuando la aplicación se inicia, pero el resto parecería que si podría ser enlazado debido a que ocurre luego de la inicialización de la aplicación. Bueno, no es tan así.

Para empezar, Application_Start, Application_End y Session_End parecen handlers pero en realidad no existe ningún evento asociado a los mismos, por lo que no es posible enlazarlos de otra manera. La clase HttpApplicationFactory es la encargada de simular estos eventos con los métodos FireApplicationOnStart(), FireApplicationOnEnd() y FireSessionOnEnd().

Segundo, el resto de los handlers no son mas que accesos directos a los eventos de distintos módulos (clases que implementan IHttpModule) que pueden ser cargados. Estos módulos son configurados en el archivo web.config en la seccion <httpModules>. Por ejemplo, uno de los que se carga por defecto es el llamado Session (de tipo System.Web.SessionState.SessionStateModule). El mismo es el encargado de mantener una sesión entre distintos requests y su carga esta definida en el archivo web.config que se encuentra en %WIN_DIR%\Microsoft.NET\Framework\[VERSION_FRAMEWORK]\CONFIG\ (este web.config se carga para todas las aplicaciones que se corran en nuestro equipo). Si en el web.config de nuestra aplicación agregamos la siguiente línea a la sección <httpModules>:

   1: <remove name="Session"/>

veremos que el handler Session_Start no se ejecuta más, ya que no hay ningún módulo llamado Session que provoque un evento Start (y como consecuencia, nuestra aplicación ya no tiene soporte para sesiones). Como no tenemos acceso a esos módulos sino hasta que la aplicación ya haya sido inicializada, no hay manera de enlazar esos eventos con los handlers y por eso necesitamos estos accesos directos que nos provee la clase HttpApplication.

Como se puede ver en el artículo original de Rick Strahl, el enlace ocurre en un método llamado HookupEventHandlersForApplicationAndModules() (con ese nombre, ¿a alguien le queda alguna duda de ello?). Resumiendo la funcionalidad del método, básicamente lo que sucede es que, dado un array de objetos MethodInfo (son cada uno de los handlers que creamos), se itera sobre ellos y se determina si el evento es de la aplicación o pertenece a algún módulo. Esto se logra buscando el ‘_’ en el nombre del mismo y tomando la primer parte. Luego se guarda una referencia a la instancia de HttpApplication o al módulo y se busca via reflection el evento en base al nombre del método (el evento ‘Start’ se asocia al método terminado en ‘Start’, por ejemplo). Un detalle curioso es que esta lógica reconoce a ‘Application_BeginRequest’ y a ‘Application_OnBeginRequest’ como lo mismo, teniendo precedencia el primero si es que se encuentran ambos presentes. Luego se valida que el handler tenga los parámetros correctos y si no es así, se genera un proxy para el mismo (lo que significa que nuestros handlers pueden no aceptar ningún parámetro y van a a ser igual de válidos). Finalmente después de obtener una referencia al evento correcto, se invoca al método Add del mismo para asociar nuestra instancia de MethodInfo (o el proxy que mencioné hace un momento) al evento y de este modo, cuando ocurra, se ejecuta nuestro handler.

¿Muy complicado? Si, seguro, la clase HttpApplication es una de las más complejas de .NET Framework, pero eso es lo que la hace interesante, ¿no?

¡Nos vemos en el próximo post!

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Posted in ASP.NET, Desarrollo Web | Etiquetado: , , , | Leave a Comment »

Generación personalizada de Ids en ASP.NET 4.0

Posted by Gerardo Contijoch en junio 12, 2009

En mi último post les comentaba sobre un gran problema que hay en ASP.NET (por lo menos hasta que salga la 4.0) con respecto a la generación automática de los ids de controles web. Resumidamente, el problema esta en que ASP.NET puede cambiar el id original que le dimos a nuestros controles (por ejemplo, txtNombre por “ctl00_ContentPlaceHolder1_txtNombre”), lo cual suele provocar que nuestros scripts en javascript deje de funcionar. Casualmente, unas horas después de publicado el post, me encuentro con éste artículo en donde se explica como en ASP.NET 4.0 esto deja de ser un problema ya que se permite la personalización del proceso de generación de ids.

Cabe aclarar que el comportamiento que voy a describir a continuación esta basado en ASP.NET 4.0 CTP y no en la versión definitiva (la cual no salió aún), pero imagino que este es uno de los puntos en donde no va a cambiar el funcionamiento. Otro punto a aclarar es que el id se ve modificado sólo del lado del cliente, es decir, el código del lado del servidor no se ve modificado en nada.

El atributo ClientIDMode

La personalización del Id ahora se puede realizar mediante un nuevo atributo de los controles web llamado ClientIDMode. Dependiendo de su valor, el id generado para el control que lo aplique puede ser fijo (siempre el mismo), dinámico (basado en datos asociados al control) o automático (es decir, igual que en las versiones anteriores de ASP.NET).

Sus valores posibles son:

  • Legacy
  • Static
  • Predictable
  • Inherit

Veamos uno por uno.

Legacy

Este valor indica que el comportamiento de ASP.NET al generar el id va a ser igual al de las versiones anteriores, es decir, el id va a tener el siguiente formato: [IdControlContenedor]_[IdControlHijo].

Static

Éste sea posiblemente el valor más utilizado ya que indica que el id generado sea el que definimos nosotros (es decir, que quede estático o fijo) y no se vea modificado por nada. Hay que tener en mente que esto puede traer conflictos de ids si hay un contenedor con un control con el mismo id dentro de este. Por ejemplo:

   1: <body>
   2:     <form id="form1" runat="server">
   3:     <div>
   4:         <asp:TextBox ID="txtNombre" runat="server" ClientIDMode="Static"></asp:TextBox>
   5:         <uc1:MiControlWeb ID="MiControlWeb1" runat="server" />
   6:     </div>
   7:     </form>
   8: </body>

Donde MiControlWeb esta definido así:

   1: <%@ Control Language="C#" AutoEventWireup="true" CodeFile="MiControlWeb.ascx.cs" Inherits="WebUserControl" %>
   2: <asp:TextBox ID="txtNombre" runat="server" ClientIDMode="Static"></asp:TextBox>

Esto quedaría renderizado así:

   1: <div>
   2:   <input name="txtNombre" type="text" id="txtNombre" />
   3:   <input name="MiControlWeb$txtNombre" type="text" id="txtNombre" />
   4: </div>

Este atributo no debería usarse nunca dentro de un control como Repeater, ya que los ids de los controles contenidos en el serían todos iguales.

Inherit

Este es el modo por defecto, y simplemente indica que el valor de ClientIDMode se hereda del contenedor donde se encuentre el control (como pueden ser un WebControl, un ContentPlaceHolder – en MasterPages- o una página).

Predictable

Este modo de generación produce resultados similares al modo Legacy pero, como lo indica su nombre, los valores son predecibles. Es principalmente útil en los controles enlazados a datos. El id en este caso se genera mediante el uso del dato asociado o enlazado al control. Veamos como funciona.

Dado este control:

   1: <asp:GridView ID="gvPersonas" runat="server" AutoGenerateColumns="false" ClientIDMode="Predictable">
   2: <Columns>
   3:     <asp:TemplateField HeaderText="Id">
   4:          <ItemTemplate>
   5:               <asp:Label ID="lblPersonaId" runat="server" Text='<%# Eval("Id") %>' />
   6:          </ItemTemplate>
   7:     </asp:TemplateField>
   8:     <asp:TemplateField HeaderText="Nombre">
   9:          <ItemTemplate>
  10:               <asp:Label ID="lblNombre" runat="server" Text='<%# Eval("Nombre") %>' />
  11:          </ItemTemplate>
  12:     </asp:TemplateField>
  13:     <asp:TemplateField HeaderText="Apellido">
  14:          <ItemTemplate>
  15:               <asp:Label ID="lblApellido" runat="server" Text='<%# Eval("Apellido") %>' />
  16:          </ItemTemplate>
  17:     </asp:TemplateField>
  18: </Columns>
  19: </asp:GridView>

El Html renderizado se vería así:

   1: <table id="gvPersonas" style="border-collapse: collapse" cellspacing="0" rules="all" border="1">
   2:   <tbody>
   3:     <tr>
   4:        <th scope="col">Id</th>
   5:        <th scope="col">Nombre</th>
   6:        <th scope="col">Apellido</th>
   7:     </tr>
   8:     <tr>
   9:        <td><span id="gvPersonas_lblPersonaId_0">1</span></td>
  10:        <td><span id="gvPersonas_lblNombre_0">Juan (primer persona)</span></td>
  11:        <td><span id="gvPersonas_lblApellido_0">Lopez</span></td>
  12:     </tr>
  13:     ...
  14:     <tr>
  15:        <td><span id="gvPersonas_lblPersonaId_35">36</span></td>
  16:        <td><span id="gvPersonas_lblNombre_35">Gerardo (36ta persona)</span></td>
  17:        <td><span id="gvPersonas_lblApellido_35">Contijoch</span></td>
  18:     </tr>
  19:   </tbody>
  20: </table>

Noten el sufijo numérico al final de los Ids. Este sufijo puede ser personalizado en los controles enlazados a datos para que sea otro valor y no un simple índice. Esto se logra mediante la aplicación del atributo RowClientIDSuffix. El valor de este atributo debe ser nombre de la propiedad o columna (dependiendo del origen de datos) cuyo valor deseamos incluir en el Id. Veamos un ejemplo para aclarar esto. Dado este código:

   1: <asp:GridView ID="gvPersonas" runat="server" AutoGenerateColumns="false" ClientIDMode="Predictable" RowClientIDSuffix="Apellido">
   2: <Columns>
   3:     <asp:TemplateField HeaderText="Id">
   4:          <ItemTemplate>
   5:               <asp:Label ID="lblPersonaId" runat="server" Text='<%# Eval("Id") %>' />
   6:          </ItemTemplate>
   7:     </asp:TemplateField>
   8:     <asp:TemplateField HeaderText="Nombre">
   9:          <ItemTemplate>
  10:               <asp:Label ID="lblNombre" runat="server" Text='<%# Eval("Nombre") %>' />
  11:          </ItemTemplate>
  12:     </asp:TemplateField>
  13:     <asp:TemplateField HeaderText="Apellido">
  14:          <ItemTemplate>
  15:               <asp:Label ID="lblApellido" runat="server" Text='<%# Eval("Apellido") %>' />
  16:          </ItemTemplate>
  17:     </asp:TemplateField>
  18: </Columns>
  19: </asp:GridView>

Se genera lo siguiente:

   1: <table id="gvPersonas" style="border-collapse: collapse" cellspacing="0" rules="all" border="1">
   2:   <tbody>
   3:     <tr>
   4:        <th scope="col">Id</th>
   5:        <th scope="col">Nombre</th>
   6:        <th scope="col">Apellido</th>
   7:     </tr>
   8:     <tr>
   9:        <td><span id="gvPersonas_lblPersonaId_Lopez">1</span></td>
  10:        <td><span id="gvPersonas_lblNombre_Lopez">Juan (primer persona)</span></td>
  11:        <td><span id="gvPersonas_lblApellido_Lopez">Lopez</span></td>
  12:     </tr>
  13:     ...
  14:     <tr>
  15:        <td><span id="gvPersonas_lblPersonaId_Contijoch">36</span></td>
  16:        <td><span id="gvPersonas_lblNombre_Contijoch">Gerardo (36ta persona)</span></td>
  17:        <td><span id="gvPersonas_lblApellido_Contijoch">Contijoch</span></td>
  18:     </tr>
  19:   </tbody>
  20: </table>

Si prestan atención, verán que el sufijo ahora es el valor del campo Apellido en nuestro origen de datos. Una característica interesante del atributo RowClientIDSuffix es que acepta más de un único valor, por lo que es posible definir nuestro control así:

   1: <asp:GridView ID="gvPersonas" ... ClientIDMode="Predictable" RowClientIDSuffix="Id, Apellido">

Generando un HTML como este:

   1: ...
   2: <tr>
   3:    <td><span id="gvPersonas_lblPersonaId_1_Lopez">1</span></td>
   4:    <td><span id="gvPersonas_lblNombre_1_Lopez">Juan (primer persona)</span></td>
   5:    <td><span id="gvPersonas_lblApellido_1_Lopez">Lopez</span></td>
   6: </tr>
   7: ...

Se puede ver que el sufijo ahora consiste en el campo Id y el campo Apellido.

Referencias:

¡Nos vemos en el próximo post!

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Posted in ASP.NET, Desarrollo Web | 1 Comment »

Acceder a variables de servidor desde Javascript

Posted by Gerardo Contijoch en junio 6, 2009

Un problema con el que nos solemos encontrar los desarrolladores web es el de acceder desde un script (en el cliente) a datos que se encuentran sólo disponibles en el servidor. No me refiero a recursos que están en el servidor, sino mas bien a valores o datos que pueden ser resultado (o formar parte) de cierto procesamiento y que no se exponen al cliente.

El caso más común con el que me encuentro yo es conocer el id de ciertos controles desde el lado del cliente. Sabemos bien que ASP.NET tiene la costumbre de renombrar nuestros controles con unos nombres bastante crípticos y esto es un gran problema si queremos acceder a ellos desde javascript. El verdadero nombre de estos controles (lo que se conoce como el ClientId) se encuentra accesible desde el código del lado del servidor, pero no desde el cliente. Y es por esto que Rick Strahl creo la clase wwScriptVariables que nos permite pasarle a nuestros scripts los valores que querramos desde el servidor.

Veamos un ejemplo con el cual vamos a trabajar. Imaginemos que tenemos una página en donde se ingresa un valor y como respuesta, se informa si ese valor es numérico o no. Esta es la parte interesante del código HTML:

   1: <div id="valor">Ingrese un valor: <asp:TextBox ID="txtValor" runat="server"></asp:TextBox>
   2:     <asp:Button ID="btnEnviar" runat="server" Text="Enviar" onclick="btnEnviar_Click" />
   3: </div>
   4: <div id="resultado">
   5:     <asp:Label ID="lblResultado" runat="server" Text=""></asp:Label>
   6: </div>

Y este es el código del lado del servidor:

   1: using System;
   2:
   3: public partial class _Default : System.Web.UI.Page
   4: {
   5:     protected void Page_Load(object sender, EventArgs e){}
   6:
   7:     protected void btnEnviar_Click(object sender, EventArgs e) {
   8:         if (EsNumero(this.txtValor.Text)) {
   9:             this.lblResultado.Text = "Es un número!";
  10:         } else {
  11:             this.lblResultado.Text = "No es un número!";
  12:         }
  13:     }
  14:
  15:     private bool EsNumero(string valor) {
  16:         int val = 0;
  17:         if (int.TryParse(valor, out val)) {
  18:             return true;
  19:         }
  20:         return false;
  21:     }
  22: }

Si ingresamos un valor numérico el resultado se vería así:

RegistrarVariablesServidor-respuesta-original

Como se ve puede ver, todo es muy sencillo, pero imaginemos que queremos aplicarle algún efecto a la página dependiendo del resultado. En mi caso voy a usar jQuery para esto y el efecto va a consistir simplemente en aplicarle un estilo a la respuesta (ya se que lo mismo se puede hacer desde el lado del servidor, pero la idea es modificarlo desde el lado del cliente como un ejemplo).

Normalmente uno agregaría algo similar a esto en la sección head de la página:

   1: <link href="style.css" rel="stylesheet" type="text/css" />
   2:
   3: <script src="jquery-1.2.6.min.js" type="text/javascript"></script>
   4:
   5: <script type="text/javascript" language="javascript">
   6:   $(document).ready(function() {
   7:
   8:     var lbl = $("#lblResultado");
   9:
  10:     lbl.addClass("claseResultado");
  11:
  12:     });
  13: </script >

Pero eso sólo funciona cuando nuestro control (en este caso el label ‘lblResultado’) no se encuentra dentro de un contenedor, como puede ser una grilla o una master page. Esto se debe a que ASP.NET modifica los Id de los elementos renderizados para evitar la colisión de nombres y por eso, nuestro ‘lblResultado’ se puede transformar en algo como ‘ctl00_ContentPlaceHolder1_lblResultado’ provocando un error en nuestro script. Lo que necesitamos es pasarle a nuestros scripts el verdadero nombre de los controles lo que se puede lograr con algunos code nuggets incrustados en medio de nuestro script:

   1: $(document).ready(function() {
   2:     var lbl = $("#<%= lblResultado.ClientId %>");
   3:     lbl.addClass("claseResultado");
   4: });

Pero esto tiene algunos problemas. Primero y principal, si nuestro script esta en un archivo externo y no embebido en la página, no se pueden usar code nuggets. Otro problema es que el valor que pasemos tiene que estar correctamente escapado para que javascript no lo malinterprete o falle. Sumado a esto, no es muy elegante la solución de incrustar código de servidor dentro de un script y no facilita para nada la reutilización de los scripts.

Ahora veamos como solucionamos este inconveniente con la ayuda de wwScriptVariables. Su uso es sencillísimo, solo hay que instanciarla y mediante el método Add() agregamos los valores que queremos pasarle a nuestros scripts. Luego, desde el lado del cliente, podemos acceder a estos valores desde una nueva variable que tendremos a nuestra disposición, la cual es generada (durante el renderizado) por la clase wwScriptVariables. Veamos como queda nuestro ejemplo con el uso de esta clase.

Del lado del servidor:

   1: using System;
   2:
   3: public partial class NuevaVersion : System.Web.UI.Page
   4: {
   5:     protected void Page_Load(object sender, EventArgs e){}
   6:
   7:     protected void btnEnviar_Click(object sender, EventArgs e) {
   8:
   9:         Westwind.Web.Controls.Controls.wwScriptVariables scriptVar = new Westwind.Web.Controls.Controls.wwScriptVariables(this, "variables");
  10:         scriptVar.Add("lblResultado", this.lblResultado.ClientID);
  11:
  12:         if (EsNumero(this.txtValor.Text)) {
  13:             scriptVar.Add("esNumero", "true");
  14:             this.lblResultado.Text = "Es un número!";
  15:         } else {
  16:             scriptVar.Add("esNumero", "false");
  17:             this.lblResultado.Text = "No es un número!";
  18:         }
  19:     }
  20:
  21:     private bool EsNumero(string valor) {
  22:         int val = 0;
  23:         if (int.TryParse(valor, out val)) {
  24:             return true;
  25:         }
  26:         return false;
  27:     }
  28: }

y del lado del cliente:

   1: $(document).ready(function() {
   2:     var lbl = $("#" + variables["lblResultado"]);
   3:
   4:     // Aplicamos distintas clases dependiendo del resultado
   5:     if (variables["esNumero"] == "true")
   6:     {
   7:         lbl.addClass("resultadoNumero");
   8:     } else {
   9:         lbl.addClass("resultadoTexto");
  10:     }
  11: });

El funcionamiento de la clase es muy sencillo, básicamente lo que hace es registrar los valores que queremos pasar al cliente y dentro del evento PreRender genera el siguiente script:

   1: <script type="text/javascript">
   2: //<![CDATA[
   3: var variables = {
   4:     "lblResultado": 'ctl00_ContentPlaceHolder1_lblResultado',
   5:     "esNumero": 'false'
   6: }
   7: //]]>
   8: </script >

Lo mejor de todo es que Rick pone a nuestra disposición el código completo de la clase para que la modifiquemos a gusto si así lo que deseamos.

La página donde pueden bajar esta clase es ésta, pero aclaro que esa es una versión vieja, hay una versión más nueva acá, la cual agrega nuevas funcionalidades como la posibilidad de actualizar el valor de las variables desde el cliente y pasárselas al servidor. Si no nos interesa esto último, les recomiendo usar la primer versión, ya que tiene menos dependencias y es mucho mas liviana.

¡Nos vemos en el próximo post!

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Posted in ASP.NET, Desarrollo Web, Javascript | 1 Comment »

Utilidad de la propiedad IsReusable de la interface IHttpHandler

Posted by Gerardo Contijoch en mayo 20, 2009

Una de las propiedades, a mi parecer, menos documentadas del .NET Framework es la propiedad IsReusable de la interface IHttpHandler. La documentación nos indica que el valor de esta propiedad determina si el handler en cuestión puede ser reutilizado por más de un request en una aplicación web, lo cual es correcto, pero en ningún lugar dice porque o porque no deberíamos reutilizar un handler, ni cuales son las consecuencias de hacerlo. En realidad no es muy difícil imaginar una interpretación el valor de esta propiedad. Puede indicarnos si nuestro handler es thread safe o no, es decir, puede ser ejecutado concurrentemente en más de un thread sin que ello afecte su funcionamiento. Otra interpretación (ver comentarios de este post) igual de válida para mi, es que esta propiedad determina si se hace pooling del handler o no. Esto significa que si el handler el reutilizable, el mismo no es destruido una vez finalizado su uso, sino que es guardado en algún lado a la espera de otro request, evitando de ese modo, la reinstanciación innecesaria de handlers de uso muy frecuente.

Yo me inclino por la primera (aunque ello no quita que la segunda pueda ser válida también).

Como regla general, si nuestro handler guarda información (en una propiedad o un campo) referente a un request en particular, entonces la propiedad debería devolver false ya que otro request podría estar siendo procesado con la información incorrecta. Acá se puede ver un ejemplo del problema este.

¡Nos vemos en el próximo post!

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Posted in Desarrollo Web | Etiquetado: , , | 2 Comments »

Restaurar ViewData luego de hacer un Redirect en ASP.NET MVC

Posted by Gerardo Contijoch en mayo 9, 2009

Una de las consecuencias de hacer un redirect desde una acción en ASP.NET MVC es que se pierde toda la información que tenemos guardada en el ViewData así como también en el ModelState. Esto se debe a que esos dos diccionario se utilizan para pasarle información desde los controladores a las vistas y si hacemos un redirect, lógicamente no vamos a estar devolviendo ninguna vista, por lo que sus valores son descartados cuando comienza la ejecución de la acción a la que redirigimos. Para evitar esto, se pueden guardar estos valores en la propiedad TempData, la cual es persistida entre requests. Como esto se puede volver un poco tedioso y repetitivo (por no decir poco elegante), decidí crear un par de ActionFilters que se encarguen de persistir valores del ViewData y de recuperarlos cuando sea necesario. El siguiente código esta basado en este post, el cual contiene la solución para persistir el ModelState.

Les presento un ejemplo de como se utilizan estos atributos y a continuación el código de los mismos.

   1: public class TestController : Controller {
   2:
   3:     [ExportarViewDataItem("Nombre")]
   4:     public RedirectToRouteResult AccionConRedirect() {
   5:         // ...
   6:         ViewData["Nombre"] = "Gerardo";
   7:         // ...
   8:         return RedirectToAction("Index");
   9:     }
  10:
  11:     [ImportarViewDataItem("Nombre")]
  12:     public ViewResult Index() {
  13:         string nombre = ViewData["nombre"].ToString();
  14:         // ...
  15:         return View();
  16:     }
  17:
  18: }

La implementación actual de los atributos requiere que se especifique un key a exportar e importar, pero los atributos son muy fácilmente modificables para que exporte e importe el ViewData completo sin necesidad de especificar cada uno de los keys.

Aca esta el código:

   1: /// <summary>
   2: /// Clase base para los atributos que exportan e importan valores de ViewData.
   3: /// </summary>
   4: public abstract class ViewDataTransferAttribute : ActionFilterAttribute {
   5:
   6:     public string Key { get; set; }
   7:
   8:     protected ViewDataTransferAttribute(string key) {
   9:         if (string.IsNullOrEmpty(key)) {
  10:             throw new ArgumentException("El parámetro key no puede ser null o vacío.", "key");
  11:         }
  12:
  13:         Key = key;
  14:     }
  15:
  16:     /// <summary>
  17:     /// Key interna para referenciar al valor guardado.
  18:     /// </summary>
  19:     /// <remarks>Se usa una key intena para asegurarnos de que no hay conflictos de keys con valores ya existentes.</remarks>
  20:     protected string InternalKey { get { return "_viewDataItem_" + Key; } }
  21: }
  22:
  23: [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
  24: public class ExportarViewDataItemAttribute : ViewDataTransferAttribute {
  25:
  26:     public ExportarViewDataItemAttribute(string key) : base(key){}
  27:
  28:     public override void OnActionExecuted(ActionExecutedContext filterContext) {
  29:
  30:         // Si no hacemos un redirect, entonces no tiene sentido respaldar los valores
  31:         if (filterContext.Result is RedirectResult || filterContext.Result is RedirectToRouteResult) {
  32:
  33:             var valor = filterContext.Controller.ViewData[Key];
  34:
  35:             if (valor != null) {
  36:                 var tempData = filterContext.Controller.TempData;
  37:                 if (tempData.ContainsKey(InternalKey)) {
  38:                     tempData.Remove(InternalKey);
  39:                 }
  40:                 tempData.Add(InternalKey, valor);
  41:             }
  42:         }
  43:
  44:         base.OnActionExecuted(filterContext);
  45:     }
  46: }
  47:
  48: [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
  49: public class ImportarViewDataItemAttribute : ViewDataTransferAttribute {
  50:
  51:     public ImportarViewDataItemAttribute(string key) : base(key) { }
  52:
  53:     public override void OnActionExecuting(ActionExecutingContext filterContext) {
  54:
  55:         var valor = filterContext.Controller.TempData[InternalKey];
  56:
  57:         if (valor != null) {
  58:             var viewData = filterContext.Controller.ViewData;
  59:             if (viewData.ContainsKey(Key)) {
  60:                 viewData.Remove(Key);
  61:             }
  62:             viewData.Add(Key, valor);
  63:             // Limpiamos TempData
  64:             filterContext.Controller.TempData.Remove(InternalKey);
  65:         }
  66:
  67:         base.OnActionExecuting(filterContext);
  68:     }
  69: }

Se puede bajar el proyecto con los atributos y los tests asociados desde aquí.

¡Nos vemos en el próximo post!

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Posted in ASP.NET MVC, Desarrollo Web | Etiquetado: , , , , | Leave a Comment »

Tooltip simple con jQuery

Posted by Gerardo Contijoch en mayo 5, 2009

Hace unos días precisé agregar un tooltip a una tabla HTML y debido a que ya estaba usando jQuery, comencé a buscar plugins y scritps para esta librería. Lo que necesitaba era un tooltip simple, sencillo, como los clásicos de Windows y lo encontré en cssglobe.com. Éste es el post donde se presenta el script. El problema que le vi a ese script es que solo funcionaba con elementos <a> y se limita a leer el atributo title de los mismos, lo cual no me servía. Es por ello que lo modifiqué para hacerlo más genérico y poder aplicarlo a cualquier elemento.

Su uso es sencillísimo:

var unElemento = $("#unDiv");
AddTooltip(unElemento, "Este es el contenido del tooltip!");

En el ejemplo anterior, el tooltip se aplicaría sobre un elemento cuyo Id es unDiv y se ve de la siguiente manera:

tooltip-con-jquery-tooltip

De hecho, se puede poner cualquier fragmento de HTML, como una imagen, una tabla o lo que queramos.

Pueden bajar el script modificado junto con código de ejemplo desde aquí.

¡Nos vemos en el próximo post!

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Posted in Desarrollo Web, Javascript, jQuery | Etiquetado: , | 1 Comment »

Aprender ASP.NET MVC

Posted by Gerardo Contijoch en abril 25, 2009

Hace poco surgió en la blogósfera, una vez mas, la discusión sobre si ASP.NET MVC es mejor o peor que ASP.NET WebForms y si vale la pena aprender a usarlo. No es la primera vez que hay una discusión de este tipo y no será la última.

Esta vez quisiera aportar mi granito de arena a la discusión y para ello les voy a contar una breve historia personal.

Comencé a trabajar con WebForms hace unos 6 años. Esa fue mi primer experiencia Web, nunca antes había trabajado mas que con HTML puro. Como la mayoría en ese momento (imagino yo), empecé con los tutoriales de Microsoft y con algún Starter Kit. Luego llegó el programa Desarrollador 5 Estrellas de Microsoft, al cual seguí. Y no olvidemos los blogs y sites como Code Project o 4 Guys From Rolla, de los cuales uno puede aprender muchísimo. Sin embargo me resulto muy complicado comenzar a trabajar. Y no era que me faltara dominio del manejo de objetos o falta de habilidades para entender conceptos de programación, ya que en cualquier otro tipo de proyectos no tenía ningún problema. Llegó el punto en que llegué a detestar el desarrollo web. No quería saber nada de la Web, ¡no entendía como había gente que le gustara! Era terriblemente complicado para mi trabajar con conceptos como controles de usuario. Entendía que de alguna manera un objeto ‘TextBox’ terminaba siendo un ‘<input>’ del lado del cliente. Entendía que el ViewState ayudaba a que no se pierdan los valores de los controles. Entendía la diferencia entre procesamiento del lado del cliente y procesamiento del lado del servidor. Sin embargo no entendía como sucedía todo eso y ese era mi problema (y es el de muchos otros). ASP.NET hace un excelente trabajo ocultándole al usuario la verdadera naturaleza de la web y del desarrollo web. ¡No fue hasta un par de años después de comenzar a trabajar con ASP.NET que entendí que era POST y GET! Es increíble, pero sí, hice desarrollo web sin saber lo que era POST y GET (entre muchas otras cosas, por supuesto). Es como conducir un tren sin saber que tiene que ir sobre rieles, el tren va derecho sin que uno haga nada, pero uno no sabe porque es así.

Afortunadamente me topé con el blog de InfinitiesLoop y su famosísimo post Truly Understanding ViewState. Créanme que no estoy exagerando cuando les digo que ese post cambio para siempre mi forma de ver ASP.NET. Fue un click, un instante en que cambió todo. Desde ese momento dejé creer en la magia de ASP.NET y comencé a buscar el porqué y el cómo de todo lo que sucedía en una página. Y fue durante esa búsqueda en la que aprendí que era realmente el desarrollo web. No podía creer todas las ‘mentiras’ (por llamarlas de alguna manera) que había aprendido de ASP.NET. Ojo, ASP.NET no es que sea malo por eso, pero el camino natural de aprendizaje de ASP.NET lo lleva a uno a pensar en terminos de ‘Textboxes’, ‘UpdatePanels’ y propiedades que ‘mágicamente’ persisten en el tiempo en un entorno ‘stateless’ (sin estado) en vez de en posts, forms y requests asincrónicos, que es lo que realmente uno necesita dominar.

¿A qué viene todo esto? Sigan leyendo.

Yo comencé a jugar con ASP.NET MVC desde la Preview 3 (ya hace casi un año) y desde el primer momento me atrapó. No había controles de servidor que me abstraigan de cosas que en realidad no debería abstraerme. No había ViewState, ya no era necesario preocuparme por que guardaba y que no guardaba ahí ni por el tamaño de este. No había más misteriosos scritps que eran inyectados en las páginas sin que lo sepamos. No tenia que preocuparme por decidir si realmente me convenía manejar un evento de un campo de texto en el cliente o del lado del servidor. No existía mas el ciclo de vida de las paginas y sus controles (vean la comparación). Era un enfoque totalmente diferente, en donde el desarrollo web solo se limita a las vistas.

Como podrán notar, tengo preferencia por ASP.NET MVC frente al desarrollo clásico con WebForms, pero eso no significa que no siga trabajando con ellos. Lo sigo haciendo, pero ahora, a diferencia de hace algunos años, se lo que hago, se los pros y los contras de usar o no usar un WebControl, se cuando conviene escribir un HttpHandler en vez de una página y se que consecuencias puede traerme el deshabilitar el ViewState para una página. Ahora tengo más control sobre lo que hago y cómo lo hago porque lo entiendo.

De todos modos prefiero ASP.NET MVC por ser mucho mas sencillo de trabajar. Es más modular (puedo hacer un site entero sin haber escrito una línea de código de lógica de negocios o haber creado las tablas en una base de datos), más fácil de testear (la forma en que esta diseñado lo permiten), es totalmente extensible o personalizable (se puede inyectar código en casi cualquier parte de la cadena de procesamiento de un request), los sites tienden a ser mas livianos (no hay mas ViewState o scripts inyectados por defecto, ni hay que renderizar nada, todo es puro html), etc. Parte de esto se debe a la aplicación del patrón MVC, y parte a la forma en que fue implementado por Microsoft.

¿Porqué aprender ASP.NET MVC?

Rob Connery menciona algunas cuestiones por las que él considera que uno debería aprender ASP.NET MVC y no puedo estar más de acuerdo con él. Sobre todo por la anteúltima: Aprender nuevos conceptos. Usando este framework uno puede aprender nuevos patrones y principios de diseño (Model-View-Controller, Repository, Dependency Injection, Inversion of control, Factory, Template, etc ), mejorar nuestras habilidades con javascript (lo más probable es que querramos darle cierta interactividad a nuestro site, y para eso usamos javascript), conocer nuevas herramientas o librerías (para javascript lo más común es usar jQuery o prototype, para IoC podemos usar un framework como Castle MicroKernel, para el acceso a datos es común ver implementaciones hechas con Linq, NHibernate y Entity Framework), y por sobre todas las cosas, ser mejores desarrolladores y estar mejor preparados para el futuro al conocer las distintas opciones que tenemos para trabajar.

[Update: Aclaro que con WebForms uno también puede aprender patrones, usar javascript, librerías, etc, pero como mencioné anteriormente, el camino natural de aprendizaje con WebForms no hace evidente todas esas cuestiones, mientras que con ASP.NET MVC, sí.]

Con todo esto no quiero decir que ASP.NET MVC les va a solucionar todos los problemas, y que deban abandonar los WebForms, para nada, pero creo que puede llegar a ser un aporte muy interesante en la carrera de un desarrollador web.

Recursos

Hay muchos recursos para aprender ASP.NET MVC, pero la mayoría esta en inglés. Entre los más recomendables se encuentran:

[Update: Se agregaron links a blogs en español sobre ASP.NET MVC.]

De ahora en adelante voy a tratar de postear más contenido relacionado a ASP.NET MVC ya que es un tema que me interesa muchísimo y hay muy poco material en español. Si les interesa y tienen alguna sugerencia o duda sobre el tema, no duden en hacerla.

¡Nos vemos en el próximo post

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Posted in ASP.NET, ASP.NET MVC, Desarrollo Web | 4 Comments »

Ventajas y desventajas del uso de Google como host de librerías javascript

Posted by Gerardo Contijoch en abril 20, 2009

Hoy en día es muy común encontrar sites que dependen de librerías javascript populares como jQuery, prototype o script.aculo.us. Normalmente estos archivos se encuentran almacenados junto al resto de las páginas de un site, lo cual puede traer consecuencias negativas.

Una de las grandes desventajas de hacerlo así es que por cada site que visitemos (y que use alguna de estas librerías), vamos a estar bajando las mismas librerías una y otra vez. Esto se debe a que para nuestros browsers los archivos http://www.misite.com/scripts/jquery.js y http://www.otrosite.com/scripts/jquery.js son archivos diferentes y en realidad lo son, su contenido puede ser el mismo, pero son dos archivos diferentes. Es por ello que si visitamos ambos sites, vamos a estar bajando dos veces el mismo contenido.

Para solucionar este problema (que puede no serlo, en ciertos casos este comportamiento es deseable), podemos recurrir a la ayuda de Google, quien hostea algunas de las librerías javascript más populares. Siguiendo con el ejemplo planteado, para referenciar a la librería de jQuery (versión 1.2.6 en este ejemplo) simplemente tenemos que usar la URL http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js (URL de la versión comprimida de jQuery, podemos quitar el .min para referenciar a la versión estándar, lo cual no sugiero).

De este modo, si www.misite.com y www.otrosite.com usan la misma referencia, nuestro browser va a bajar solo una vez el archivo .js, aumentando la velocidad de carga de los sites (debido a que ya vamos a tener en el caché el archivo bajado).

Y eso no es todo, ya a que si estamos bajando el archivo desde un server diferente al que hostea nuestro site, los browsers modernos pueden bajar las librerías paralelamente a los archivos de nuestro site gracias al multithreading. Si los archivos se encontraran en el mismo server, la cantidad de bajadas simultáneas se vería reducida para evitar la sobrecarga del mismo.

Otro beneficio de usar Google como host es que los archivos que bajemos de ahí, los vamos a estar bajando de servers posiblemente más veloces y en redes con mayor ancho de banda que los nuestros.

Siempre hay un pero

Usar esta técnica tiene sus desventajas también, como lo muestra Derek Allard en este post. La primera de ella es ¿qué sucede si hay un problema con los servidores de Google? Pues simplemente no vamos a tener acceso a las librerías de las cuales depende nuestro site. Derek propone una solución a esto en su post, pero a mi no me funcionó. Imagino que su solución tampoco es válida cuando tenemos plugins u otro tipo de dependencias ya que la carga de las mismas depende de que se haya cargado con anterioridad la librería en cuestión.

Otra de las desventajas es que Google es quien tiene control sobre el código de las librerías, lo que significa que tienen acceso a nuestro código y datos (cuando usamos las librerías, les estamos pasando nuestra información a ellas para que la procesen y las mismas pueden haber sido modificadas para registrar esa información). Si bien no hay que ponerse paranoicos, es un factor a tener en cuenta. También es verdad que muy posiblemente estén guardando información sobre el trafico de nuestro site (contando la cantidad y origen de descargas de los archivos).

Leí en algún comentario de algún blog (el cual no encuentro) que algunos antivirus bloquean la bajada de scripts de otros severs, lo cual tiene sentido desde el punto de vista de la seguridad si uno lo piensa un poco.

Casos especiales

Hay otros casos en los que simplemente nos conviene hostear nosotros mismos esos archivos.

No tiene sentido buscar las librerías en servidores externos durante la etapa de desarrollo, estaríamos haciendo que la carga de las mismas sea más lenta ya que durante el desarrollo de un site, normalmente trabajamos de forma local y siempre va a ser más rápido cargarlas de esa forma.

Otro caso en donde no es viable el uso de esta técnica es cuando el site que usa las librerías es un site interno, es decir, se utiliza en una Intranet y no se publica en internet. Una vez mas, es posiblemente más rápido descargar los scripts desde un servidor en la red local que desde internet.

¡Nos vemos en el próximo post!

Referencias:

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Posted in Desarrollo Web, Javascript | Etiquetado: , , | Leave a Comment »

Mis problemas con ASP.NET AJAX

Posted by Gerardo Contijoch en abril 5, 2009

Mi relación con ASP.NET AJAX nunca fue buena. Siempre tuve muchos problemas con la implementación de AJAX de Microsoft. Nunca me gustó usarla, me da la impresión de que es muy pesada y hay que saber demasiado para usarla (los problemas de éste y éste post son un ejemplo de ello). Por cuestiones de trabajo, hace unos meses me vi obligado a usarla y todavía me sigo encontrando con problemas de no muy obvia solución.

Recuerdo cuando usé AJAX por primera vez. Fue con Ajax.NET Professional. Tenia que manejar todo con javascript y no había controles ni nada de eso, el HTML se generaba en el servidor y se enviaba como respuesta al request. Por supuesto que esto no tiene porque ser así necesariamente, se puede generar el HTML en el cliente, pero aún así era mas sencillo de usar. Uno tenia que ensuciarse un poco mas las manos con javascript, pero por lo menos uno tenia control sobre lo que hacía. No me pasa lo mismo con ASP.NET AJAX, no se que es lo que pasa por detrás, no se cuando puede fallar ni donde, el framework inyecta scripts por todos lados… no me termina de convencer. Ojo, no quiero decir que Ajax Pro sea mejor que ASP.NET AJAX, simplemente digo que la solución de Microsoft no me da la seguridad que busco.

Por ejemplo, el caso que mencioné en este post. El error que obtenía era básicamente ‘no se puede cargar el archivo xxxx.js’. Resulta que para cargar un archivo de scripts correctamente (dadas ciertas condiciones, no ocurre siempre) tengo que modificarlo de modo que el mismo notifique que su carga ha finalizado. No es algo muy intuitivo que digamos… Uno puede buscar en muchos lados la respuesta hasta encontrarla.

Otro de los problemas que tengo es el uso de los UpdatePanels. Si no se saben usar, pueden resultar muy peligrosos. Éste post muestra un ejemplo de ello. Creo que una de las características más peligrosas de los UpdatePanels es justamente su facilidad de uso. Da la impresión de que al arrastrar el control a una pagina se nos solucionan muchos problemas, pero podemos estar trayendo muchos mas. Para evitarlo, hay que entender como funcionan y saber como hacerlos funcionar como queremos, pero lamentablemente no es mucha la gente que se toma el trabajo (o tiene tiempo) de leer sobre el funcionamiento interno de los controles y por ello muchas veces son más un problema que una solución.

No se… no me termina de convencer todo esto, supongo que será hasta que me acostumbre…

¿A alguien le sucede lo mismo?

¡Nos vemos en el próximo post!

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Posted in ASP.NET, Desarrollo Web | Etiquetado: , , | 4 Comments »