¿Problemas con la tecla Alt-Gr en Live Writer?

Desde que actualicé a Live Writer 2011 venía arrastrando un problema que, aunque fácilmente evitable, me hacía perder demasiado tiempo al escribir entradas técnicas en el blog.

El problema era el siguiente: al intentar introducir los caracteres arroba “@” (AltGr+2) o almohadilla “#” (AltGr+3), lo cual es bastante habitual cuando se habla de C# o Razor por ejemplo, el párrafo actual se reformatea  como encabezado de nivel 2 y nivel 3, respectivamente. O en otras palabras, AltGr+N era un atajo para convertir en encabezado de nivel N (<h2>, <h3>…) el párrafo que estábamos editando en ese instante.

La causa es un error de la compilación 15.4.3502.922 de Live Writer 2011, que fue corregido poco después de su aparición. Para ver la versión actual, sólo tienes que ir al menú “Live Writer” (donde tienes las opciones de Abrir, Guardar, etc.) y seleccionar la opción “Acerca de Windows Live Writer”:

Acerca de Windows Live Writer antes de actualizar

Tras dedicar un par de minutos a Googlear, encontré que a primeros de Diciembre habían publicado una actualización que corregía el problema. Así pues, basta con ir al sitio web del producto y descargar la última versión, que nos lo actualizará a la compilación 15.4.3508.1109:

Windows Live Writer actualizado

A partir de ese momento, el problema estará resuelto.

Conclusión: cuando hay problemas molestos como éste, a veces hay que dedicar un par de minutos a solucionarlos, en lugar de soltar un “ya lo miraré más adelante” y adaptarse al problema. Eso se llama procrastinación.

Crosspossting dessde: http://www.variablenotfound.com/2011/02/problemas-con-la-tecla-alt-gr-en-live.html

WebGrid en MVC 3, paso a paso

ASP.NET MVC Una de las preguntas que recibo con más frecuencia vía formulario de contacto en el blog, en sus comentarios, y en los cursos de MVC es cómo implementar grids, las habituales rejillas de datos que se suelen utilizar en interfaces de gestión de datos, con capacidades de paginación y ordenación.

Dado que hasta MVC 3 no había un soporte oficial para implementar esta funcionalidad desde la propia plataforma, nos veíamos obligados a buscarnos un poco la vida, y o bien usar el andamiaje generado por Visual Studio y desarrollar a mano las paginaciones y ordenaciones (¡uf!), o utilizar componentes externos (como el de MVCContrib, Telerik, jqGrid u otros).

La última versión de ASP.NET MVC, de la mano de la tecnología WebPages, nos trae un nuevo conjunto de helpers de productividad bastante interesantes en el espacio de nombres System.Web.Helpers, entre los cuales encontramos WebGrid, que por fin ofrece una solución “de serie” potente y flexible para ayudarnos a implementar esta funcionalidad de uso tan frecuente en nuestras aplicaciones.

Vamos a ver, paso a paso y de forma totalmente práctica, cómo utilizar WebGrid. Al final de post, además, encontraréis un enlace para descargar el proyecto completo para VS2010+SQL Express.

1. Lo primero: el Modelo

Estructura de la tabla en la base de datosLo primero que necesitamos antes de empezar a profundizar en el helper WebGrid es un Modelo, las entidades de datos de nuestra aplicación, así como los mecanismos que nos permitan hacer persistente y recuperar la información desde el sistema de almacenamiento que estemos utilizando.

En este caso, vamos a utilizar como almacén una base de datos SQL Express con una única tabla, en la que hemos creado una tabla para guardar datos de personas; esta colección es la que pretendemos mostrar en forma de rejilla de datos, por lo que necesitamos poblarla con algunos datos para probar más adelante.

Entity Data Model Para acceder a la base de datos vamos a utilizar Entity Framework, por lo que  necesitaremos también un Entity Data Model, un modelo conceptual de entidades, que podemos agregar a la carpeta Models del proyecto utilizando la opción “Agregar nuevo elemento” del menú contextual. En el asistente que aparece, sólo debemos indicarle que vamos a generar el modelo desde una base de datos existente, seleccionamos la tabla Personas, y listo.

Con esto tenemos ya la infraestructura básica de datos de nuestra aplicación. El siguiente paso será crear una clase de servicios, que será la que provea la lógica de negocio y acceso a datos para nuestro sistema.

Obviamente esto no tiene por qué ser siempre así, depende de nuestras necesidades y de la arquitectura del software, y en nuestro ejemplo van a ser ambos aspectos bastante simples.

El código inicial de nuestra clase de servicios es el siguiente:

public class ModelServices: IDisposable
{
    private readonly DatosEntities _datos = new DatosEntities();
 
    public IEnumerable<Persona> ObtenerPersonas()
    {
        return _datos.Personas.ToList();
    }
 
    public void Dispose()
    {
        _datos.Dispose();
    }
}

Como se puede observar en el código anterior, tenemos un único método, llamado ObtenerPersonas(), que retorna el conjunto completo de personas almacenadas en la base de datos.

Y de momento, he aquí todo el Modelo que necesitamos de momento.

2. El controlador

Nuestra clase del controlador, que llamaremos PersonasController, en este primer acercamiento va a ser realmente sencilla. Una única acción, Index(), que retornará una vista a la que suministraremos la colección de datos obtenida desde el Modelo:

public class PersonasController : Controller
{
    public ActionResult Index()
    {
        var datos = new ModelServices().ObtenerPersonas();
        return View(datos);
    }
}

Y esto es todo: una acción con dos líneas. Vale, podría haber sido una única línea, pero así el código me parecía más legible 😉

3. La vista, toma primera: WebGrid entra en escena

Ahora es cuando vamos a empezar a notar las ventajas de utilizar WebGrid respecto a las opciones disponibles hasta MVC y que ya hemos citado al principio del post.

Observad el siguiente código, una vista Razor que recibe una enumeración de objetos Persona desde el controlador y genera un grid completo:

@model IEnumerable<WebGridDemo.Models.Persona>
@{
    ViewBag.Title = "Personas";
    WebGrid grid = new WebGrid(Model);
}
<h2>Personas</h2>
@grid.GetHtml()

Impresionante, ¿eh? Aunque pueda parecer increíble, el código anterior es todo lo que necesitamos para montar un grid funcionalmente completo, con paginación y ordenación por columnas: ¡dos líneas! En la primera de ellas instanciamos el WebGrid suministrándole la colección de datos sobre la que debe iterar, y en la segunda (ya al final) generamos el marcado HTML que enviaremos al cliente.

El resultado en ejecución lo podemos ver en la siguiente captura de pantalla:

WebGrid en ejecución

Aunque todavía queda algo lejos de la perfección, el resultado es espectacular.

El helper, en su comportamiento por defecto, muestra una columna por cada propiedad que encuentra en la clase sobre la que itera, eso sí, ordenándolas alfabéticamente. Además, ha utilizado el nombre de las propiedades como encabezado de columna, y las muestra como enlaces para forzar la ordenación por cada una de ellas, e incluso ha introducido en el pie un sistema completo de navegación por las páginas de datos.

Y lo mejor que todo esto funciona directamente, sin necesidad de añadir una línea de código más 🙂

Sin embargo, como siempre, estos automatismos tienen su precio. Por un lado, no estamos controlando las columnas a mostrar, ni el formato en que sus valores son presentados (observad, por ejemplo, la fecha de nacimiento), ni sus encabezados… Normalmente, necesitaremos esforzarnos algo más (aunque no demasiado) para dejarlo todo perfecto.

Además, existe un serio problema de rendimiento cuando el número de elementos del grid sea importante: tanto la ordenación como la paginación se realizan en memoria con el número total de elementos. Exagerando, si tenemos un millón de filas en la base de datos, se materializarán en memoria un millón de objetos, serán ordenados según el criterio actual y, finalmente, sólo serán mostrados al cliente los diez objetos que contiene una página de datos. Veremos más adelante que hay fórmulas para gestionar de forma eficiente estos escenarios.

4. La vista, toma segunda: las columnas que yo quiera, por favor

Existen distintas fórmulas para especificar las columnas a mostrar en un WebGrid. La primera de ellas, es mediante exclusión sobre el conjunto total de propiedades. Entre muchos otros aspectos, en el método GetHtml() podemos especificar un array de nombres de propiedad que no deben mostrarse como columnas. Por ejemplo, sobre el ejemplo anterior, si no nos interesa mostrar la propiedad IdPersona, podríamos haber sustituido la última línea de la vista por:

@grid.GetHtml(exclusions: new[] {"IdPersona"})

Sin embargo, el enfoque anterior no es demasiado útil, puesto que normalmente querremos indicar el orden de aparición de las columnas, especificar sus encabezados, determinar si las columnas pueden ser utilizadas como criterios de ordenación, etc. Toda esta información se define en objetos WebGridColumn.

Aunque hay otras formas de hacerlo, habitualmente encontraremos en el parámetro columns de la llamada a GetHtml() un array con el detalle de las columnas del grid, como en el siguiente ejemplo:

@grid.GetHtml(columns: new [] {
    grid.Column("Nombre"),
    grid.Column("Apellidos"),
    grid.Column("EMail"),
    grid.Column("FechaNacimiento"),
    grid.Column("NumeroDeHijos"),
})

Como se puede observar, estamos pasando en el parámetro columns un array en el que cada elemento lo estamos generando mediante una llamada al método Column() de WebGrid, en cuyo primer parámetro indicamos el nombre de la propiedad a la que corresponde la columna.

El resultado de la ejecución del código utilizando este último código sería:

Web grid en ejecución, con mis columnas

Algo ha mejorado la cosa, aunque todavía tenemos que afinar algo más en cuanto a la presentación.

5. La vista, toma tercera: las columnas como yo quiero, por favor

Todavía nos quedan varios detalles por apuntalar para que el grid, al menos a efectos visuales, cumpla un mínimo razonable. Para personalizar cada columna, podemos utilizar los parámetros del método generador de columnas Column() que ya hemos visto anteriormente:

  • header, que permite indicar el texto mostrado en el encabezado,
  • canSort, que indica si la columna puede ser utilizada como criterio de ordenación,
  • format, que permite indicar un formato personalizado para el contenido de la columna,
  • style, que indica la clase CSS que se aplicará a todas las celdas de la columna.

De todas ellas, sólo merece una especial mención la propiedad format. En ella podemos indicar, bien mediante un bloque de marcado Razor, bien mediante una función lambda, cómo debe formatearse el contenido de la propiedad vinculada a la columna.

En el primer caso, debemos comenzar el bloque de marcado con el carácter de escape de Razor (@) y seguirlo del código que queremos enviar al cliente. Desde su interior podemos hacer referencia al objeto que está siendo evaluado utilizando @item, como en el siguiente ejemplo, donde se muestra cómo formatear la columna EMail para que sea mostrada como un hiperenlace de tipo mailto:

    grid.Column("EMail", 
                 format: @<a href="mailto:@item.Email">@item.Email</a>
    )

Para profundizar en la «magia» que hace posible la utilización de bloques de marcado donde debería haber código, no os perdáis este magnífico post de Eduard Tomás sobre Razor Templates.

También podemos utilizar una función lambda, que recibe como parámetro el objeto actual y retorna una cadena (o un IHtmlString si no debe ser codificada). Por ejemplo, a continuación vemos cómo utilizar esta posibilidad para dar formato a la columna FechaNacimiento:

    grid.Column("FechaNacimiento", 
                format: p=>p.FechaNacimiento.ToShortDateString()
    )

Por tanto, teniendo en cuenta todo lo anterior, podemos tunear un poco el grid utilizando el siguiente código. Como recordatorio, mostraré de nuevo el código completo de la vista, para que podáis observar en su conjunto cómo va quedando:

@model IEnumerable<WebGridDemo.Models.Persona>
@{
    ViewBag.Title = "Personas";
    WebGrid grid = new WebGrid(Model);
}
<h2>Personas</h2>
@grid.GetHtml(columns: new [] {
    grid.Column("Nombre", canSort: false),
    grid.Column("Apellidos"),
    grid.Column("EMail", 
                 format: @<a href="mailto:@item.Email">@item.Email</a>
    ),
    grid.Column("FechaNacimiento", 
                header: "Fecha de nacimiento",
                format: p=>p.FechaNacimiento.ToShortDateString()
    ),
    grid.Column("NumeroDeHijos", 
                header: "Número de hijos",
                style: "a-la-derecha"
    )
})

En ejecución ya sí que podemos ver algo más terminado:

WebGrid, con las columnas con formato

6. La vista, toma cuarta: ¿y no puedo añadir columnas personalizadas?

¡Pues claro!

De hecho, basta con añadir columnas exactamente igual que las anteriores, excepto en que no las vincularemos a ninguna propiedad de la clase del Modelo. Esto, combinado con la flexibilidad del formateo personalizado (parámetro format), nos ofrece ya todo lo que necesitamos para crear columnas totalmente a nuestro antojo.

El siguiente código muestra cómo añadir una columna adicional con enlaces hacia las acciones que permitirían, por ejemplo, editar o eliminar una Persona:

@grid.GetHtml(columns: new [] {

    ... // Resto de columnas del grid, vistas anteriormente
    grid.Column(
          "", 
          header: "Acciones",
          format: @<text>
                    @Html.ActionLink("Editar",   "Edit",   new { id=item.IdPersona} )
                    |
                    @Html.ActionLink("Eliminar", "Delete", new { id=item.IdPersona} )
                  </text>
    )
})

Observad que esta vez, para incrementar la legibilidad del código, estamos utilizando el tag especial <text> de Razor, que nos permite crear bloques de marcado de varias líneas.

Y el resultado, como el siguiente:

WebGrid, con columnas personalizadas

7. La vista, toma quinta: mejor casi que lo quiero todo a mi gusto

El helper WebGrid ofrece multitud de opciones de personalización adicionales que podemos establecer tanto al llamar a sus distintos métodos como de forma directa. Por ejemplo, GetHtml() permite indicar los siguientes parámetros, además de los ya vistos:

  • headerStyle, footerStyle, rowStyle, alternatingRowStyle, y selectedRowStyle permite indicar las clases CSS a aplicar a las filas de encabezado, pie, filas de datos alternativas, y fila seleccionada, respectivamente.
  • caption, para especificar un título para la tabla, que será incluido en una etiqueta <caption> en el encabezado.
  • fillEmptyRows, establecido a true hace que cada página tenga siempre el mismo número de filas, creando filas en blanco si fuera necesario.
  • emptyRowCellValue indica el valor a mostrar en las celdas de filas vacías.
  • mode, permite especificar el tipo de paginador a generar, eligiéndolo mediante una combinación de elementos de la enumeración WebGridPagerModes:
    • WebGridPagerModes.Numeric: el paginador mostrará enlaces directos a páginas cercanas a la actual.
    • WebGridPagerModes.FirsLast: se mostrarán enlaces para ir a la primera y última página de datos.
    • WebGridPagerModes.NextPrevious: aparecerán enlaces para desplazarse a la página anterior y siguiente.
    • WebGridPagerModes.All: todos los anteriores al mismo tiempo.
  • numericLinksCount: indica el número de páginas que aparecerán, siempre que mode contenga el valor WebGridPagerModes.Numeric.
  • firstText, previousText, nextText, lastText, permite sustituir los textos que aparecen por defecto en los enlaces de ir a la primera, anterior, siguiente y última página respectivamente. Inicialmente son los habituales “<<”, “<”, “>”, y “>>”.

Por ejemplo, observad el siguiente código, y su resultado en ejecución una vez hemos creado un par de reglas en la hoja de estilos del sitio web:

...
<h2>Personas</h2>
@grid.GetHtml(
    fillEmptyRows: true,
    alternatingRowStyle: "fila-alternativa",
    headerStyle: "encabezado-grid",
    footerStyle: "pie-grid",
    mode: WebGridPagerModes.All,
    firstText: "<< Primera",
    previousText: "< Anterior",
    nextText: "Siguiente >",
    lastText: "Última >>",
    columns: new [] {
     ... // Definición de columnas vista anteriormente
})

WebGrid, con algo de estilo

Asimismo, el propio constructor de WebGrid permite modificar también numerosos aspectos funcionales del grid mediante los siguientes parámetros:

  • defaultSort, que indica la columna que actuará como ordenación por defecto mientras no se especifique otra.
  • rowsPerPage (por defecto, 10), define el número de filas que aparecerán en cada página.
  • canPage, canSort, indican respectivamente si el grid va a permitir paginación y ordenación. Por defecto, true en ambos casos.
    fieldNamePrefix, que permite indicar el prefijo que será utilizado en los parámetros del query string utilizados por el grid. Esto, por ejemplo, permitiría mostrar varios grids simultáneamente sobre la misma página, sin interferir en su funcionamiento.
  • selectionFieldName, sortFieldName, sortDirectionFieldName permiten indicar el nombre de los parámetros usados para mantener el estado de, respectivamente, la fila seleccionada, el campo de ordenación y el sentido de la ordenación.

WebGrid permite incluso el funcionamiento en modo Ajax, es decir, es capaz de mostrar las distintas páginas de datos sin necesidad de recargar la página completa. En este caso, podemos utilizar los parámetros ajaxUpdateContainerId y ajaxUpdateCallback, que permiten indicar respectivamente el elemento de la página donde serán mostrados los datos y una función callback que será llamada tras actualizar el elemento. Esto, si no se me olvida, lo veremos en otro post más adelante.

Y recapitulando…

A lo largo de este artículo hemos ido profundizando en el uso de WebGrid de forma progresiva, partiendo de un ejemplo realmente simple hasta abordar escenarios de personalización más avanzados. Espero que sirva para mostrar el uso y principales características de este útil y potente helper, que con toda seguridad nos ahorrará bastante trabajo en los proyectos MVC 3.

Sin embargo, ya he comentado anteriormente que no es oro todo lo que reluce… la paginación implementada por defecto por WebGrid es bastante ineficiente, puesto que necesita tener en memoria el conjunto de datos completo para ordenarlo y extraer únicamente la página solicitada. No es nada complicado implementar de forma correcta esta paginación, pero dado que este post ya ha quedado lo suficientemente extenso, dejaremos su explicación para un artículo posterior.

Descargar proyectoAh, podéis descargar el proyecto de demostración desde mi Skydrive (requiere VS2010, MVC 3 y SQL Express).

Publicado en: Variable not found.