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, 30 de junio de 2008
Una cascadaLos desplegables en cascada (también llamados cascading dropdowns o enlazados) son elementos muy frecuentes en todo tipo de aplicaciones, pues suponen una gran ayuda al usuario y dotan de mucho dinamismo al interfaz.

En pocas palabras, consiste en llenar una lista desplegable con elementos elegidos en función de una decisión previa, como la selección en otra lista. El ejemplo típico lo encontramos en aplicaciones donde debemos introducir una dirección y nos ponen por delante un desplegable con las provincias y otro con los municipios, y en éste último sólo aparecen aquellos que pertenecen a la provincia seleccionada.

Esto, que en aplicaciones de escritorio no tiene dificultad alguna, en el entorno Web se complica un poco debido a la falta de comunicación entre la capa cliente (HTML, javascript) y servidor (ASP, .NET, JSP, PHP...), propia de un protocolo desconectado como HTTP.

Años atrás (e incluso todavía hoy) una forma de solucionar el problema es forzando un postback, es decir, una recarga de la página completa, con objeto de llenar el desplegable dependiente en función del valor seleccionado en el principal. Supongo que no es necesario detallar los inconvenientes: tiempos de espera, asombro del usuario ante el pantallazo, problemas asociados al mantenimiento del estado...

La aparición de Ajax (¡en minúsculas!) supuso una revolución para las aplicaciones Web, añadiendo a las amplias capacidades de Javascript para la manipulación del interfaz e información en cliente mecanismos ágiles para conseguir comunicación en tiempo real con el lado del servidor.

En este post explicaré cómo conseguir desplegables en cascada utilizando la plataforma ASP.NET MVC (Preview 3) en el lado servidor y Javascript en el lado cliente, apoyándome en la librería jQuery, que tanto juego da. Aprovecharé de paso para comentar algunos aspectos interesantes de la preview 3 del framework MVC, de jQuery y de novedades de C# 3.0. Tocho de post, por tanto ;-)

El resultado será un formulario como el mostrado en la siguiente imagen:

El proyecto en ejecución

Antes de nada: estructurar la solución

Para lograr el objetivo pretendido respetando el patrón MVC, debemos contar con los siguientes elementos:
  • El modelo, que representará la información manejada por el sistema. Para no complicar el ejemplo (o complicarlo, según se mire ;-)), vamos a crear una colección en memoria sobre la que realizaremos las consultas.

  • La vista, que será un formulario en el que colocaremos los dos desplegables. El primero de ellos permitirá seleccionar una categoría tomada del Modelo, y el segundo mostrará los elementos de dicha categoría de forma dinámica.

  • El controlador, en el que incluiremos acciones, en primer lugar, para inicializar y mostrar el formulario y, en segundo lugar, para obtener la lista de elementos relacionados con la categoría seleccionada. Esta última acción será invocada por la vista cuando el primer desplegable cambie de valor.

Primero: el modelo

En escenarios reales será el conjunto de entidades que representen el modelo de datos del sistema. En este caso montaremos un ejemplo muy simple, basado en un diccionario en memoria, y un par de métodos de consulta de la información en él mantenida.
public class Datos
{
private static Dictionary<string, string[]> datos =
new Dictionary<string, string[]>
{
{ "Animal", new[] { "Perro", "Gato", "Pez"} },
{ "Vegetal", new[] { "Flor", "Árbol", "Espárrago"} },
{ "Mineral", new[] { "Cuarzo", "Pirita", "Feldespato"} }
};

public static IEnumerable<string> GetCategories()
{
return datos.Keys;
}

public static IEnumerable<string> GetElementsForCategory(string category)
{
string[] els = null;
datos.TryGetValue(category, out els);
return els;
}
}

Destacar del código anterior los siguientes aspectos:
  • La información se almacenará en un Dictionary<string,string[]>. Esto, por si no estás muy familiarizado con los generics o tipos genéricos, sólo quiere decir que se trata de un diccionario cuya clave será de tipo string (las categorías) y el valor será un array de strings (los elementos asociados a cada categoría).

    De esta forma, podremos obtener fácilmente la lista de categorías simplemente enumerando las claves (Keys) del diccionario, y los elementos contenidos en una categoría sólo con devolver el valor asociado a la misma, como vemos en los métodos GetCategories() y GetElementsForCategories().

  • Para inicializar el diccionario he utilizado un inicializador de colecciones, una característica de C# 3.0 ya comentada en un post anterior.

  • Los dos métodos de consulta de datos retornan un IEnumerable<string> (¡me encantan los generics!), flexibilizando el tipo devuelto. Así, podríamos devolver cualquier tipo de objeto que implemente este interfaz, prácticamente todas las listas, colecciones y arrays, siempre que contengan en su interior un string.


Segundo: la vista

Para nuestro ejemplo sólo necesitamos una vista, el formulario con los desplegables, que crearemos en el archivo Index.aspx. Además de componer el interfaz, la vista gestionará la interacción con el usuario haciendo que ante la selección de una opción del desplegable de categorías se invoque al controlador para solicitarle la lista de elementos de la categoría seleccionada, y cargar con ella el segundo desplegable.

El código fuente del proyecto completo lo podéis encontrar en un enlace al pie del post, pero incluiré aquí las porciones más importantes. En primer lugar, el formulario HTML se compone así:
<form method="post" action="#">
<fieldset><legend>Formulario</legend>
<label for="ddCategory">Categoría:</label>
<%= Html.DropDownList("ddCategory") %>
<label for="ddElement">Elemento:</label>
<%= Html.DropDownList("ddElement") %>
</fieldset>
</form>

Fijaos un momento en el código. Los desplegables, en lugar de utilizar el tag HTML <select> se han definido utilizando los métodos de ayuda (Html helpers) incluidos en el framework MVC. Estos, además de generar las etiquetas apropiadas, realizan otra serie de tareas como establecer valores de la lista, que nos ahorrarán mucho tiempo. En el controlador veremos cómo se le inyectan los elementos (los distintos <option>) desde código de forma muy sencilla.

jQuery logoDespués del formulario, llegamos a la parte de scripting de la página, la implementación de la obtención y llenado del desplegable de elementos en función de la opción seleccionada, al más puro estilo Ajax. Utilizaremos jQuery, pero los conceptos son idénticos para otros sistemas; de hecho, esto mismo podríamos realizarlo con cualquier otra librería Javascript que permita enviar peticiones JSON, e incluso, aunque es bastante más trabajoso, sería posible hacerlo "a pelo" utilizando las facilidades nativas (HttpRequest).
<script type="text/javascript">

// Inicialización

$(document).ready(function() {
$("#ddCategory").change(function() {
cambiaElementos($("#ddCategory").val());
});
cambiaElementos($("#ddCategory").val());
});

// Carga el desplegable de elementos en función
// de la categoría que le llega como parámetro.


function cambiaElementos(cat) {

var dd = document.getElementById("ddElement");
dd.options.length = 0;
dd.options[0] = new Option("Espere...");
dd.selectedIndex = 0;
dd.disabled = true;

// Control de errores

$("#ddElement").ajaxError(function(event, request, settings) {
dd.options[0] = new Option("Categoría incorrecta");
});

// Obtenemos los datos...

$.getJSON(
'<%= Url.Action("GetElements") %>', // URL a la acción
{ category: cat }, // Objeto JSON con parámetros
function(data) { // Función de retorno exitoso
$.each(data, function(i, item) {
dd.options[i] = new Option(item, item);
});
dd.disabled = false;
});
}
</script>
 
En la primera parte se utiliza el evento jQuery ready, ejecutada cuando la página está lista para ser manipulada, para introducir código de inicialización. En este momento se asocia al evento change del desplegable de categorías la llamada apropiada a la función cambiaElementos(), pasándole la categoría seleccionada. También se aprovecha para realizar la primera carga de elementos.

Carga del desplegable en procesoSeguidamente está la función cambiaElementos, que se encargará de actualizar el desplegable con los elementos asociados a la categoría seleccionada. Para ello, en primer lugar, se introduce en el dropdown un elemento con el texto "Espere...", que aparecerá mientras el sistema obtiene los datos desde el servidor, a la vez que se deshabilita el control para evitar manipulación del usuario.

Después se define la función que se ejecutará si durante la llamada Ajax se produce un error. Para ello utilizamos el evento ajaxError, al que asociamos una función anónima que, simplemente, cargará en el desplegable secundario el texto "Categoría incorrecta" y lo dejará deshabilitado.

Por último, utilizamos el método getJSON para efectuar la llamada al servidor y realizar la carga del desplegable con la información devuelta. Son tres los parámetros que se le envían al método:
  • La URL de la acción, obtenida utilizando el helper Url.Action que devuelve la dirección de la acción cuyo nombre le pasamos como parámetro.

  • El objeto, en formato JSON, uyas propiedades representan los parámetros a enviar al controlador.

  • La función a ejecutar cuando se reciban datos del servidor. El parámetro de entrada data contendrá el array de string con el que tenemos que llenar el desplegable, enviado desde el servidor según la notación JSON.

    Aunque el recorrido de este vector y la carga de opciones en el control podría haberse realizado de forma sencilla mediante un bucle, he utilizado el método each para ilustrar el uso de este tipo de iteradores, tan de moda y muy al estilo funcional empleado, por ejemplo, en las expresiones lambda. Este método recibe dos parámetros; el primero es la colección sobre la cual se va a iterar, y el segundo es una función que se ejecutará para cada uno de los elementos recorridos.


Tercero: el controlador

Es el intermediario en toda esta historia: recibe las peticiones del cliente a través la invocación de acciones, ejecuta las tareas apropiadas y provoca la aparición o actualización de las vistas, utilizando para ello las clases ofrecidas por el Modelo.

El controlador define dos acciones: la primera de ellas (Index) es la encargada de cargar los valores iniciales de los desplegables y enviar al usuario la vista correspondiente (Index.aspx). La segunda es la responsable de devolver un objeto en notación JSON con los elementos de la categoría enviada como parámetro.

El código es el siguiente:

public class HomeController : Controller
{
public ActionResult Index()
{
ViewData["ddCategory"] = new SelectList( Datos.GetCategories() );
ViewData["ddElement"] = new SelectList( new [] {"(Selecciona)"} );
return View();
}

public ActionResult GetElements(string category)
{
IEnumerable<string> elements = Datos.GetElementsForCategory(category);
if (elements == null)
throw new ArgumentException("Categoría " + category +" no es correcta");

Thread.Sleep(1000); // Simulamos tiempo de proceso...

return Json(elements);
}
}
 
Hay varios puntos que me gustaría destacar en este código. En primer lugar, observad que ya las acciones siguen la nueva convención introducida en la preview 3, retornando objetos de tipo ActionResult. En el primer método, el return View() (sin parámetros) hace que el sistema envíe al cliente la vista cuyo nombre coincide con la acción actual (es decir, Index.aspx); en el segundo método se retorna un objeto de tipo JSonResult que serializará la información suministrada en formato JSON.

También es destacable la ayuda introducida en la tercera Preview para llenar listas de formularios utilizando la clase SelectList. Como podéis ver, basta con crear en el diccionario ViewData un elemento con el mismo nombre que el del desplegable y asignarle una instancia del SelectList inicializada con una colección de elementos (cualquier tipo que implemente IEnumerable). Él se encargará, al renderizar la página, de iterar sobre la colección y crear las etiquetas <option> oportunas.

Asimismo, se puede observar el uso del Modelo en ambos métodos para leer los datos. En este caso se utiliza una clase que encapsularía la lógica de obtención de información, pero nada impediría, por ejemplo, asumir que las clases del Modelo son las generadas desde el diseñador de Linq2Sql y utilizar consultas Linq desde el Controlador.

Observad también el lanzamiento de la excepción cuando la categoría especificada no existe. Lo que llega al navegador en este caso es un error HTTP que es capturado por la función que especificamos para el evento ajaxError, haciendo que en el desplegable enlazado aparezca un mensaje de error.

Por último, la inclusión de la orden Thread.Sleep() es por pura estética, para que se vea lo bien que queda el mensaje "Espere..." en el desplegable mientras carga los datos. En un escenario real este tiempo sería consumido por las comunicaciones cliente-servidor y la obtención de datos desde el modelo, que habitualmente utilizará una base de datos u otro mecanismo de persistencia.

Descargar proyecto Descargar proyecto (Visual Web Developer Express 2008 + SP1 + ASP.NET MVC Preview 3).

Publicado en: http://www.variablenotfound.com/.
lunes, 23 de junio de 2008
NUnitLa inclusión en el SP1 (Beta) de Visual Web Developer Express 2008 de soporte para librerías de clases y aplicaciones Web, abriendo el uso del framework MVC Preview 3 para los que estamos aprendiendo con esta herramienta, era sin duda una novedad sorprendente, pero no la única.

Joe Cartano comentaba hace unas semanas, en el blog oficial del equipo de Visual Web Developer , las nuevas mejoras de este IDE, y en especial las relativas al soporte para plantillas de terceros, y publicó de paso unas estupendas plantillas para la creación de tests unitarios basados en NUnit en aplicaciones ASP.NET MVC.

Para instalarlas, basta con descargar el archivo (con el enlace anterior), descomprimirlo sobre un directorio cualquiera y ejecutar el archivo .BAT correspondiente a la versión de Visual Studio que estemos utilizado, siempre con permisos de administrador. Si todo va bien y suponiendo además que tenemos NUnit instalado, al crear una nueva aplicación ASP.NET MVC en VWD Express aparecerá un cuadro de diálogo como el siguiente:

Diálogo de creación de un proyecto de tests
Esto va en la línea de lo comentado por ScottGu meses atrás, en sus primeros posts sobre el nuevo framework MVC, donde aseguraba que se podría utilizar cualquier framework de testing, citando algunos como NUnit y MBUnit. Y lo de permitir una cierta integración de estos frameworks ajenos a la casa en la versión Express es todo un detalle.

Si se acepta el diálogo, se creará dentro de la misma solución un proyecto de tipo librería que contendrá las referencias a ensamblados necesarios para su ejecución (NUnit, routing, Mvc, etc.) y con un par de métodos de prueba predefinidos. De esta forma, para comprobar el correcto funcionamiento de nuestras aplicaciones sólo tendremos que ejecutar los tests, aunque de momento, y a diferencia de las versiones superiores de Visual Studio 2008, tendrá que ser desde fuera del entorno, utilizando la consola de NUnit:

WDExpress 2008 y NUnit en ejecución

Publicado en: www.variablenotfound.com.
martes, 17 de junio de 2008
Visual Studio Logo
Cuando hace unas semanas ScottGu anunció la Preview 3 de ASP.NET MVC, quedé muy sorprendido de que al final no viniera con soporte para Visual Web Developer Express 2008, sobre todo teniendo en cuenta que un mes antes había adelantado este acontecimiento para los que estamos probando la plataforma MVC con esta herramienta.

Pero no, era una falsa alarma. Scott había olvidado comentarlo, y días más tarde rectificaba en un nuevo post en el que comunicaba que sí sería posible usar la Preview 3 desde la versión Express de Web Developer sin necesidad de usar plantillas específicas.

Desde entonces estaba deseando tener un ratillo para probarlo, y por fin ha llegado el momento.

En primer lugar, he de aclarar que, aunque pueda parecer lo contrario, no se trata de que el equipo de Microsoft haya introducido cambios en la plataforma MVC para hacerla compatible con la versión Express, sino al revés. Son los nuevos cambios introducidos en el IDE, y principalmente su recién estrenado soporte a proyectos de tipo Aplicación Web, los que hacen posible que funcione directamente sobre esta herramienta.

Y este es el motivo de que, antes que nada, sea necesario instalar el SP1 de .NET Framework 3.5 y de las herramientas Express (aún en Beta). Y si váis a hacerlo, mucho ojo, que todas las páginas donde aparece para descargar aparece llena de warnings; en otras palabras, no recomiendan su instalación en máquinas de verdad, sólo en entornos de prueba. Pero no os preocupéis, que para eso estoy yo ;-)

Después de un proceso algo lento y un par de reinicios del equipo, el Service Pack 1 Beta queda instalado sin problemas. Sólo falta descargar y montar la Preview 3 de ASP.NET MVC para que al ir a crear un nuevo proyecto desde Visual Web Developer aparezcan la plantilla MVC, entre otras novedades:

Plantillas disponibles en Web Developer Express 2008 con SP1 Beta

Y para probar, nada mejor que la aplicación de demostración sobre Northwind que preparó Phil Haack hace unas semanas. Salvo un ligero error de carga debido a que la versión Express no soporta la creación de proyectos de solución (no podremos probar los tests unitarios integrados en VS2008), todo funciona a la perfección.

Northwind demo para MVC CTP 3

Sí señor. Ahora sí que podemos seguir jugando. :-)

Publicado en: www.variablenotfound.com.
miércoles, 11 de junio de 2008
PuzzleAunque es lo que más mola, los desarrolladores no siempre podemos trabajar con los últimos lenguajes y tecnologías. De hecho, solemos pasar gran parte de nuestro tiempo manteniendo y mejorando sistemas basados en plataformas que consideramos obsoletas o de menor potencia que las habituales, lo que nos lleva a tener que dar respuesta "artesana" a problemas que usando tecnologías más apropiadas estarían solucionados.

Hoy por ejemplo, desarrollando con VbScript en un entorno propietario embebido en un sistema de gestión empresarial hemos necesitado crear GUIDs y hemos visto que el lenguaje no dispone de esta capacidad, a diferencia de .Net o Java, que proporcionan vías para conseguirlo de forma muy sencilla.

Preguntándole a Google, que todo lo sabe, he encontrado varias soluciones aplicables de forma directa.

La primera, es usar funciones propias del motor de base de datos. En nuestro caso concreto, la solución está conectada a un SQL Server, que afortunadamente dispone desde la versión 2000 de una función integrada de generación de GUIDs. Así pues, podríamos utilizar un código como el siguiente:
' Obtiene un nuevo GUID
' Parámetro: una conexión abierta
function ObtenerGUID(conn)
dim rs
set rs = conn.Execute("Select NEWID()")
if (not rs.eof) then
ObtenerGUID = rs(0)
end if
end function
 
Pero, ¿y si no usas SQL Server? No pasa nada, prácticamente todos los motores de bases de datos incorporan algún mecanismo equivalente, como MySQL ("UUID()") y Oracle ("SYS_GUID()").

En este momento podríamos decir que tenemos una buena solución... siempre que tengamos a mano una conexión a una base de datos capaz de generar un GUID. ¿Y si no fuera este el caso? Aquí va otro código que utilizando un objeto Scriptlet.TypeLib, utilizado por Windows como soporte a los lenguajes de scripting:
' Obtiene un nuevo GUID
Function ObtenerGUID()
dim typeLib, guid
set typeLib = CreateObject("Scriptlet.TypeLib")
guid = typeLib.Guid
ObtenerGUID= left(guid, len(guid)-2)
set typeLib = Nothing
End Function
 
También hay quien utiliza llamadas a UUIDGEN.EXE, una utilidad proporcionada normalmente con kits de desarrollo de .NET y los entornos Visual Studio, generando el GUID sobre un archivo de texto para más tarde leerlo desde el script, pero me ha parecido una solución demasiado compleja para el problema.

Por último he encontrado unos ejemplos de código en el centro de soporte de Microsoft que muestran varias maneras de crear identificadores únicos, aunque me quedo con la más completa, consistente en generar la secuencia de caracteres aleatorios:
' Obtiene un GUID tipo Windows
' 8+4+4+4+12 caracteres A-Z y 0-9
Function CreateWindowsGUID()
CreateWindowsGUID = CreateGUID(8) & "-" & _
CreateGUID(4) & "-" & _
CreateGUID(4) & "-" & _
CreateGUID(4) & "-" & _
CreateGUID(12)
End Function

' Obtiene un GUID del tamaño indicado
' Parámetro: longitud del GUID
Function CreateGUID(tmpLength)
Randomize Timer
Dim tmpCounter,tmpGUID
Const strValid = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
For tmpCounter = 1 To tmpLength
tmpGUID = tmpGUID & Mid(strValid, Int(Rnd(1) * Len(strValid)) + 1, 1)
Next
CreateGUID = tmpGUID
End Function


Publicado en: www.variablenotfound.com.
domingo, 8 de junio de 2008
Cuestiones enviadas por lectoresRespondiendo a una consulta que hacía Joaquín hace un par de días, hoy describiremos una forma de hacer más atractivos los cuadros de edición de nuestros formularios web, introduciéndoles iconos o imágenes que, a la vez que adornan bastante, pueden ayudar al usuario a saber qué información debe introducir.

Pero para que quede claro lo que pretendemos, primero un ejemplo del resultado que vamos a conseguir:


La forma de conseguirlo es bastante sencilla. Basta con establecer, en las propiedades de estilo de los cuadros de edición una imagen de fondo con el icono que queremos incluir, y dejar un espaciado por la izquierda (padding-left) equivalente al ancho del mismo para que la introducción del texto comience a partir de ese punto.

Por ejemplo, si definimos las siguientes clases en el CSS de nuestra página (y suponiendo que la ruta de las imágenes sea correcta, claro):
 .lupa
{
background: white url(icono_lupa.gif) no-repeat 2px center;
padding: 2px 2px 2px 18px;
}
.telefono
{
background: white url(icono_telefono.gif) no-repeat 2px center;
padding: 2px 2px 2px 18px;
}
 
Como se puede observar, se establece un fondo blanco con una imagen cuya URL se especifica (icono_xxxx.gif), mostrada sin repetición (no-repeat), posicionada en coordenada horizontal 2px y centrada verticalmente. El padding izquierdo será de 18px para que comience ahí el área de edición, a la derecha de la imagen.

Podremos utilizar después en nuestro HTML un código como el siguiente para conseguir que los cuadros de edición apararezcan "adornados" como nos interese en cada momento eligiendo para cada uno de ellos la clase CSS apropiada:
 <input type="text" class="lupa" />
<input type="text" class="telefono" />
 
Espero que esto responda la duda, Joaquín.

Y por cierto, he utilizado esta técnica en el buscador del encabezado del blog, que lo tenía un poco soso...

Publicado en: www.variablenotfound.com.
martes, 3 de junio de 2008
Ya comenté un día mi fascinación por los motores de física en tiempo real. Siguiendo con este tema, os voy a presentar hoy otro engine con el que me he topado.

Box2DFlashAS3 es un port para ActionScript 3.0 de una librería originalmente escrita para C++, Box2D. Ahí van unas demostraciones de sus posibilidades (pulsad las teclas izquierda y derecha para cambiar la demo, "R" para resetear la vista y también podéis arrastrar objetos con el ratón):

Hace unos meses se publicó su última revisión, la 4.1.3, y se distribuye bajo licencia zlib/libpng, lo que permite su libre utilización incluso con fines comerciales.

Publicado en: www.variablenotfound.com.