Mostrando entradas con la etiqueta asp.net. Mostrar todas las entradas
Mostrando entradas con la etiqueta asp.net. Mostrar todas las entradas
miércoles, 16 de febrero de 2011
Hace unos días hablaba de ObjectInfo y ServerInfo, dos helpers presentes en System.Web.Helpers, un componente compartido por ASP.NET MVC 3 y WebPages. Hoy vamos a revisar la clase estática
El primero de los sentidos, CLR—>JSON, no es algo que no pudiéramos hacer antes utilizando los serializadores Javascript, pero este componente nos lo pone algo más fácil. Por ejemplo, dada la siguiente clase del Modelo:
… y un controlador en el que suministramos a la vista una instancia de dicha clase:
En la Vista podríamos introducir un código como el siguiente para volcar sobre la salida la representación JSON del objeto del modelo. Observad que hasta ahora para conseguir lo mismo teníamos que escribir bastante más código:
Fijaos también que el resultado de
Por otra parte, también podemos utilizar la clase
Publicado en: Variable not found.
Json
, también definida en dicho espacio de nombres, que podemos utilizar para codificar objetos en formato JSON (JavaScript Object Notation) y viceversa, crear instancias de objetos partiendo de un texto JSON.El primero de los sentidos, CLR—>JSON, no es algo que no pudiéramos hacer antes utilizando los serializadores Javascript, pero este componente nos lo pone algo más fácil. Por ejemplo, dada la siguiente clase del Modelo:
… y un controlador en el que suministramos a la vista una instancia de dicha clase:
En la Vista podríamos introducir un código como el siguiente para volcar sobre la salida la representación JSON del objeto del modelo. Observad que hasta ahora para conseguir lo mismo teníamos que escribir bastante más código:
Fijaos también que el resultado de
Json.Encode
(bueno, y la alternativa anterior también), debemos pasarlo por una llamada a Html.Raw()
para evitar que sea codificado a HTML antes de enviarlo al cliente. En cualquier caso, el resultado puede ser asignado directamente a una variable, puesto que el marcado generado es la representación JSON del objeto suministrado listo para su uso, como puede observarse en el resultado de procesar la vista anterior:Por otra parte, también podemos utilizar la clase
Json
para hacer la conversión inversa, es decir, construir un objeto del CLR desde un string
que representa un objeto codificado en JSON. Como en el caso anterior, también podíamos utilizar JavascriptSerializer
para conseguirlo, pero ahora podemos hacerlo más rápidamente:Publicado en: Variable not found.
Publicado por José M. Aguilar a las 11:32 a. m.
Hay sólo 1 comentario, ¡participa tú también!
Etiquetas: asp.net, aspnetmvc, desarrollo, javascript
martes, 15 de febrero de 2011
En posts anteriores hemos hablado sobre el helper WebGrid (aquí y aquí), y las facilidades que nos ofrece a la hora de crear rejillas de datos con paginación y ordenación. En todos los ejemplos que vimos, cada cambio de página o de criterio de ordenación forzaba una recarga completa de la vista, lo cual es bastante correcto y válido en la mayoría de escenarios.
Sin embargo, WebGrid también ofrece la posibilidad de recargar (al menos visualmente, como veremos después) únicamente la rejilla de datos, por lo que se consigue un comportamiento muy al estilo Ajax, como el que podemos conseguir con otras soluciones. Y de nuevo de forma realmente sencilla, en dos simples pasos.
En primer lugar, en la instanciación del objeto
Y finalmente, ya dentro del marcado de la vista, debemos asegurar que la llamada a
Y eso es todo. Sin necesidad de modificar el Controlador, ni por supuesto el Modelo, las operaciones de desplazamiento entre páginas, cambio de columna o sentido de ordenación, no recargarán la página completa, sino únicamente el contenedor indicado.
Parece magia, eh? ;-)
En este script se utiliza el método load() de jQuery para solicitar de forma asíncrona la página completa, invocando la misma acción actual, y suministrándole los parámetros que necesita, o sea, la página actual, orden o sentido de ordenación, dependiendo de la operación realizada.
Del resultado de esta petición Ajax (la página completa de nuevo), jQuery seleccionará sólo el fragmento cuyo identificador coincida con el indicado por
De esa forma, se habrá actualizado el grid sin necesidad de recargar visualmente la página completa, aunque internamente sí se habrá generado y enviado al cliente completa. La verdad, esta forma de conseguir el efecto Ajax, un poco a lo bestia, recuerda un poco al celebérrimo UpdatePanel :-O.
Y ya puestos a criticar (constructivamente siempre, claro ;-)), seguro que podría haberse conseguido una implementación no intrusiva, de forma que un usuario con los scripts desactivados obtuviese el funcionamiento “por defecto” del Grid, con recarga total de la página.
Pero bueno, al menos es interesante saber que WebGrid cuenta con la posibilidad de ajaxificarse, y conocer el funcionamiento de esta característica, por si en alguna ocasión nos encontramos con ella.
Dejo en SkyDrive un proyecto de demostración de WebGrid en modo Ajax (VS2010+MVC3+SQLExpress), así como los ejemplos de los post anteriores sobre el mismo tema, por si os interesa verlo en funcionamiento.
Publicado en: Variable not found.
Sin embargo, WebGrid también ofrece la posibilidad de recargar (al menos visualmente, como veremos después) únicamente la rejilla de datos, por lo que se consigue un comportamiento muy al estilo Ajax, como el que podemos conseguir con otras soluciones. Y de nuevo de forma realmente sencilla, en dos simples pasos.
En primer lugar, en la instanciación del objeto
WebGrid
, debemos suministrar un valor al parámetro ajaxUpdateContainerId
, indicando el identificador del elemento en cuyo interior generaremos la rejilla de datos, por ejemplo así: WebGrid grid = new WebGrid(
..., // Otros parámetros del constructor
ajaxUpdateContainerId: "contenedor-grid"
);
Y finalmente, ya dentro del marcado de la vista, debemos asegurar que la llamada a
GetHtml()
del helper se realice dentro de un contenedor con el identificador especificado anteriormente:
<div id="contenedor-grid">
@grid.GetHtml(
fillEmptyRows: true,
alternatingRowStyle: "fila-alternativa",
headerStyle: "encabezado-grid",
footerStyle: "pie-grid",
mode: WebGridPagerModes.All,
...
})
</div>
fillEmptyRows: true,
alternatingRowStyle: "fila-alternativa",
headerStyle: "encabezado-grid",
footerStyle: "pie-grid",
mode: WebGridPagerModes.All,
...
})
</div>
Y eso es todo. Sin necesidad de modificar el Controlador, ni por supuesto el Modelo, las operaciones de desplazamiento entre páginas, cambio de columna o sentido de ordenación, no recargarán la página completa, sino únicamente el contenedor indicado.
Parece magia, eh? ;-)
Tras bambalinas
Pues no, no hay nada mágico, lo que está ocurriendo por detrás es bastante simple: al activarse el modo Ajax, simplemente por el hecho de asignar un valor aajaxUpdateContainerId,
WebGrid modificará la forma de generar los enlaces del paginador y las columnas ordenables, de forma que no guiarán al navegador hacia la acción de refresco, sino ejecutarán un script.En este script se utiliza el método load() de jQuery para solicitar de forma asíncrona la página completa, invocando la misma acción actual, y suministrándole los parámetros que necesita, o sea, la página actual, orden o sentido de ordenación, dependiendo de la operación realizada.
Del resultado de esta petición Ajax (la página completa de nuevo), jQuery seleccionará sólo el fragmento cuyo identificador coincida con el indicado por
ajaxUpdateContainerId
, y sustituirá el contenido del elemento con ese mismo identificador en la vista actual. De esa forma, se habrá actualizado el grid sin necesidad de recargar visualmente la página completa, aunque internamente sí se habrá generado y enviado al cliente completa. La verdad, esta forma de conseguir el efecto Ajax, un poco a lo bestia, recuerda un poco al celebérrimo UpdatePanel :-O.
Y ya puestos a criticar (constructivamente siempre, claro ;-)), seguro que podría haberse conseguido una implementación no intrusiva, de forma que un usuario con los scripts desactivados obtuviese el funcionamiento “por defecto” del Grid, con recarga total de la página.
Pero bueno, al menos es interesante saber que WebGrid cuenta con la posibilidad de ajaxificarse, y conocer el funcionamiento de esta característica, por si en alguna ocasión nos encontramos con ella.
Dejo en SkyDrive un proyecto de demostración de WebGrid en modo Ajax (VS2010+MVC3+SQLExpress), así como los ejemplos de los post anteriores sobre el mismo tema, por si os interesa verlo en funcionamiento.
Publicado en: Variable not found.
lunes, 7 de febrero de 2011
Días atrás comentábamos por aquí cómo utilizar el helper WebGrid de MVC 3 para crear rejillas de datos, partiendo de un ejemplo muy sencillo, apenas un par de líneas de código, e introduciendo sucesivas mejoras hasta llegar a un interfaz bastante aceptable para manejar conjuntos de datos con paginación, ordenación, y columnas personalizadas.
Sin embargo, también comentábamos que la pasmosa simplicidad con la que podíamos poner en marcha un grid completo tenía su coste: por defecto, WebGrid necesita disponer de una copia en memoria del conjunto completo de datos sobre el que actúa. Sí, completo :-O.
Es decir, si tenemos un millón de filas de nuestra base de datos y queremos mostrarlas en un orden concreto y por páginas, debemos suministrar a WebGrid una colección con el millón de entidades materializadas en memoria; el helper las ordenará según el criterio activo en ese momento, contará el total de elementos, calculará las páginas necesarias para mostrarlos en función del número de elementos por página definidos, y obtendrá el subconjunto de instancias correspondientes a la página actual. Tras ello, generará el grid y el mecanismo de paginación que podemos ver en su pie.
Obviamente, el impacto en el rendimiento de nuestro sistema puede ser terrible. La materialización de entidades no es un proceso rápido, como tampoco lo es la ordenación en memoria cuando el número de elementos es importante. Y lo peor es que el procedimiento completo se repite para cada petición y usuario conectado, lo que hace que utilizar los mecanismos de paginación y ordenación por defecto no sea una buena idea la mayoría de las veces.
Sin duda es bastante mejor delegar al motor de base de datos las tareas de ordenación y selección de filas, y materializar en memoria sólo las entidades que sean necesarias en la vista actual, es decir, la página de datos visible. ¿Lo hacemos?
El primero de ellos,
El código de este método es bastante simple:
Asumiendo que estamos utilizando LINQ, la obtención de una página concreta de datos es trivial utilizando los operadores
Este enfoque, aunque bastante sencillo y rápido de implementar tiene algunas contraindicaciones. El hecho de escribir estas expresiones en el interior de cadenas de texto hace que éstas sean inmunes a refactorizaciones o comprobaciones en tiempo de compilación, lo que aumenta la probabilidad de aparición de errores difíciles de detectar. Asimismo, estamos confiando ciegamente en que la expresión es eSQL sintáctica y semánticamente correcto, por lo que o bien nos arriesgamos a que reviente en tiempo de ejecución si no introducimos las comprobaciones oportunas, o bien debemos delegar a otro componente (por ejemplo, al Controlador) la responsabilidad de construir siempre expresiones válidas.
En este caso, el método del Modelo recibirá, además de la página actual y el número de filas por página, un árbol de expresión que contiene la expresión de ordenación, y un elemento de la enumeración
También se puede ver que se aprovecha de la ejecución diferida de LINQ to SQL para ir especificando de forma sucesiva las distintas cláusulas de la consulta. Ésta no será ejecutada hasta el final, en la llamada al método
De esta forma, conseguimos que desde el Controlador podamos obtener la página de datos, por ejemplo, así:
Como principal inconveniente, decir que no es tan potente y flexible como la anterior. Por ejemplo, en este caso no podemos indicar de forma inmediata más de una propiedad de ordenación, cosa que antes sí era muy sencillo. Eso sí, en nuestro escenario, dado que vamos a ordenar según columnas independientes del grid, tampoco este aspecto es demasiado importante.
En definitiva, existen gran cantidad de posibilidades para implementar el método del Modelo que retorna las instancias pertenecientes a una página concreta de datos atendiendo a los criterios de ordenación especificados. Aquí hemos visto un par de ellas, pero basta con pararse a pensarlo un rato y fácilmente podremos idear un buen puñado más de soluciones.
Vamos a pasar a estudiar ahora qué cambios debemos realizar en el Controlador para realizar la paginación correcta.
Ya en el cuerpo del método, la obtención del número de personas es inmediata gracias al método
A diferencia del ejemplo del post anterior, en el que la Vista sólo necesitaba una lista de personas para maquetarse, en esta ocasión debemos suministrarle alguna información más:
Una solución muy simple aunque bastante cándida e inocentona, basada en la primera de las implementaciones del Modelo que hemos visto anteriormente, podría ser la siguiente:
Este error es fácil de evitar simplemente introduciendo código que compruebe estos parámetros y establezca valores por defecto en caso de existir algún problema, como el mostrado a continuación, donde comprobamos que la columna de ordenación se encuentra en una “lista blanca” de columnas permitidas, y que la ordenación es ascendente o descendente:
Antes de pasar a la Vista, veamos el código completo del controlador pasando los criterios de ordenación al Modelo mediante cadenas. Exceptuando las líneas introducidas para mejorar la legibilidad del código, apenas llegamos a la docena de líneas para completar la acción:
A continuación, tras establecer el título de la página, instanciamos el WebGrid suministrándole a su constructor el valor del parámetro
Finalmente, invocamos a su método
Hemos visto distintas alternativas para implementar el Modelo, basadas en Entity Framework. No son las únicas posibilidades ni mucho menos, pero espero al menos que os den ideas para implementar vuestras propias soluciones. También hemos reescrito el Controlador, implementando el consumo de estos servicios del Modelo para obtener justo la información que necesitamos para mostrar una página de datos, y la hemos pasado a la Vista, a la que hemos tenido que hacer sólo ligeras modificaciones.
Finalmente, como el post ha quedado algo extenso, repasamos rápidamente qué es necesario hacer para implementar un WebGrid con una paginación eficiente:
Como en otras ocasiones, podéis descargar el proyecto desde SkyDrive (requiere VS2010, SQL Express y ASP.NET MVC 3). ¡Que aproveche! ;-)
Publicado en: Variable not found.
Sin embargo, también comentábamos que la pasmosa simplicidad con la que podíamos poner en marcha un grid completo tenía su coste: por defecto, WebGrid necesita disponer de una copia en memoria del conjunto completo de datos sobre el que actúa. Sí, completo :-O.
Es decir, si tenemos un millón de filas de nuestra base de datos y queremos mostrarlas en un orden concreto y por páginas, debemos suministrar a WebGrid una colección con el millón de entidades materializadas en memoria; el helper las ordenará según el criterio activo en ese momento, contará el total de elementos, calculará las páginas necesarias para mostrarlos en función del número de elementos por página definidos, y obtendrá el subconjunto de instancias correspondientes a la página actual. Tras ello, generará el grid y el mecanismo de paginación que podemos ver en su pie.
Obviamente, el impacto en el rendimiento de nuestro sistema puede ser terrible. La materialización de entidades no es un proceso rápido, como tampoco lo es la ordenación en memoria cuando el número de elementos es importante. Y lo peor es que el procedimiento completo se repite para cada petición y usuario conectado, lo que hace que utilizar los mecanismos de paginación y ordenación por defecto no sea una buena idea la mayoría de las veces.
Sin duda es bastante mejor delegar al motor de base de datos las tareas de ordenación y selección de filas, y materializar en memoria sólo las entidades que sean necesarias en la vista actual, es decir, la página de datos visible. ¿Lo hacemos?
1. El Modelo, revisitado
Recordemos que en el post anterior teníamos una clase de servicios del Modelo simplísima, con un único método llamadoObtenerPersonas()
, que utilizaba un contexto de objetos de Entity Framework para obtener todas las instancias de la clase Persona almacenadas en la base de datos:public class ModelServices: IDisposable
{
private readonly DatosEntities _datos = new DatosEntities();
public IEnumerable<Persona> ObtenerPersonas()
{
return _datos.Personas.ToList();
}
public void Dispose()
{
_datos.Dispose();
}
}
En este caso, dado que pretendemos delegar al Modelo la responsabilidad de paginar y ordenar adecuadamente los datos antes de enviarlos a WebGrid, necesitamos añadirle dos nuevos métodos: ContarPersonas()
, y ObtenerPaginaDePersonas()
.El primero de ellos,
ContarPersonas()
, que retornará el número de elementos totales existentes en la base de datos. Ya hemos comentado antes que WebGrid podía obtener este dato simplemente contando el total de elementos presentes en el origen de datos, pero como ahora pretendemos pasarle sólo los datos de la página actual, esta información debemos indicársela expresamente para que pueda construir el mecanismo de paginación correctamente.El código de este método es bastante simple:
public int ContarPersonas()
{
return _datos.Personas.Count();
}
Vamos ahora a implementar ObtenerPaginaDePersonas()
. Como seguro podréis intuir, su objetivo es retornar el conjunto de instancias de Persona que deben aparecer en una página concreta de datos, teniendo en cuenta el número de elementos por página empleado y el criterio de ordenación actual. Asumiendo que estamos utilizando LINQ, la obtención de una página concreta de datos es trivial utilizando los operadores
Skip()
y Take()
. La única dificultad con la que podemos encontrarnos es la forma de pasar al Modelo el criterio de ordenación actual, pero, como podemos comprobar a continuación hay muchas formas de conseguirlo, casi tantas como desarrolladores que se enfrenten a ella. Para que os hagáis una idea, veremos dos posibilidades: utilizando expresiones de cadena, y basada en expresiones lambda.1.1. ObtenerPaginaDePersonas(), magic string edition
Una posibilidad bastante sencilla de implementar consiste en que el Modelo reciba directamente una cadena de caracteres en la que se encuentre la expresión de ordenación utilizada. Si podemos asegurar, además, que esta cadena venga ya escrita en Entity SQL, simplemente tendríamos que insertar la cláusula de ordenación apropiada:public IEnumerable<Persona>
ObtenerPaginaDePersonas(int paginaActual, int personasPorPagina, string criterioOrdenacion)
{
if (paginaActual<1) paginaActual = 1;
return _datos.Personas
.OrderBy(criterioOrdenacion)
.Skip((paginaActual - 1) * personasPorPagina)
.Take(personasPorPagina)
.ToList();
}
De esta forma, podemos invocar al Modelo pasándole directamente expresiones como “it.Apellidos DESC
”, “it.FechaNacimiento ASC
”, o incluso combinaciones como “it.Apellidos DESC, it.Nombre DESC
”, que funcionarán perfectamente. Observad que la sintaxis debe ser eSQL (de ahí el prefijo “it.
” que llevan delante las propiedades del objeto actual).Este enfoque, aunque bastante sencillo y rápido de implementar tiene algunas contraindicaciones. El hecho de escribir estas expresiones en el interior de cadenas de texto hace que éstas sean inmunes a refactorizaciones o comprobaciones en tiempo de compilación, lo que aumenta la probabilidad de aparición de errores difíciles de detectar. Asimismo, estamos confiando ciegamente en que la expresión es eSQL sintáctica y semánticamente correcto, por lo que o bien nos arriesgamos a que reviente en tiempo de ejecución si no introducimos las comprobaciones oportunas, o bien debemos delegar a otro componente (por ejemplo, al Controlador) la responsabilidad de construir siempre expresiones válidas.
1.2. ObtenerPaginaDePersonas(), lambda edition
Si queremos evitar el envío de información en cadenas de texto, podemos seguir el enfoque propuesto por LINQ y utilizar árboles de expresión para trasladar los criterios de ordenación al Modelo.En este caso, el método del Modelo recibirá, además de la página actual y el número de filas por página, un árbol de expresión que contiene la expresión de ordenación, y un elemento de la enumeración
Direccion
indicando si debe ordenarse ascendente o descendentemente. El código podría ser como el siguiente:public IEnumerable<Persona> ObtenerPaginaDePersonas<T>(
int paginaActual, int personasPorPagina,
Expression<Func<Persona,T>> ordenacion,
Direccion direccion)
{
if (paginaActual<1) paginaActual = 1;
IQueryable<Persona> query = _datos.Personas;
if (direccion == Direccion.Ascendente)
query = query.OrderBy(ordenacion);
else
query = query.OrderByDescending(ordenacion);
return query.Skip((paginaActual - 1) * personasPorPagina)
.Take(personasPorPagina)
.ToList();
}
Como se puede observar, se trata de un método genérico para dar cobertura a los distintos tipos de propiedad por los que es posible ordenar, que es el tipo de retorno del árbol de expresión ordenacion
.También se puede ver que se aprovecha de la ejecución diferida de LINQ to SQL para ir especificando de forma sucesiva las distintas cláusulas de la consulta. Ésta no será ejecutada hasta el final, en la llamada al método
ToList()
, que es cuando realmente se materializarán las instancias de Persona presentes en la página de datos actual.De esta forma, conseguimos que desde el Controlador podamos obtener la página de datos, por ejemplo, así:
var personas = _services.ObtenerPaginaDePersonas(
page,
personasPorPagina,
p=>p.NumeroDeHijos, // Expresión ordenación
Direccion.Descendente // Ascendente);
Observad que, a diferencia de la técnica que vimos anteriormente, aquí sí que usamos tipado fuerte para indicar la ordenación. Un cambio de nombre de la propiedad NumeroDeHijos
, por ejemplo, sería refactorizada correctamente o, cuanto menos, generaría un error en compilación.Como principal inconveniente, decir que no es tan potente y flexible como la anterior. Por ejemplo, en este caso no podemos indicar de forma inmediata más de una propiedad de ordenación, cosa que antes sí era muy sencillo. Eso sí, en nuestro escenario, dado que vamos a ordenar según columnas independientes del grid, tampoco este aspecto es demasiado importante.
En definitiva, existen gran cantidad de posibilidades para implementar el método del Modelo que retorna las instancias pertenecientes a una página concreta de datos atendiendo a los criterios de ordenación especificados. Aquí hemos visto un par de ellas, pero basta con pararse a pensarlo un rato y fácilmente podremos idear un buen puñado más de soluciones.
Vamos a pasar a estudiar ahora qué cambios debemos realizar en el Controlador para realizar la paginación correcta.
2. El Controlador
Si habéis descargado el ejemplo del post anterior u os habéis peleado un poco con WebGrid, habréis notado que el mantenimiento del estado de la rejilla se realiza propagando entre llamadas una serie de parámetros en el query string que, por defecto, son los siguientes:- page, que indica el número de página actual,
- sort, la columna por la que estamos ordenando,
- sortDir, la dirección de ordenación actual, indicada con las constantes “ASC” o “DESC”.
public ActionResult Index(int page = 1, string sort = "Apellidos", string sortDir = "ASC")
{
// Obtener el número total de personas
// Obtener la colección de personas de la página actual
// Retornar la vista suministrándole esos datos
}
Observad el uso de parámetros opcionales para la acción, en los que especificamos un valor por defecto para cuando no estén indicados de forma explícita en la petición.Ya en el cuerpo del método, la obtención del número de personas es inmediata gracias al método
ContarPersonas()
que hemos creado anteriormente en el Modelo. La obtención de las personas para poblar la rejilla depende del enfoque utilizado en la implementación del método ObtenerPaginaDePersonas()
. A continuación veremos algunas posibilidades, aunque antes vamos a pararnos en otro detalle.A diferencia del ejemplo del post anterior, en el que la Vista sólo necesitaba una lista de personas para maquetarse, en esta ocasión debemos suministrarle alguna información más:
- el número de personas por página,
- la colección de personas, aunque sólo las presentes en la página actual,
- el número total de personas, para que WebGrid pueda crear la herramienta de navegación por páginas.
public class PaginaDePersonasViewModel
{
public int NumeroDePersonas { get; set; }
public IEnumerable<Persona> Personas { get; set; }
public int PersonasPorPagina { get; set; }
}
De esta forma, podemos ir ya dando forma al cuerpo de la acción, que quedaría de la siguiente manera:public ActionResult Index(int page = 1, string sort = "Apellidos", string sortDir = "ASC")
{
const int personasPorPagina = 10;
var numPersonas = _services.ContarPersonas(); var personas = ... // Obtener la colección de personas
var datos = new PaginaDePersonasViewModel()
{
NumeroDePersonas = numPersonas,
PersonasPorPagina = filasPorPagina,
Personas = personas
};
return View(datos);
}
Como comentábamos anteriormente, la obtención de las personas a mostrar en la página actual dependerá de la implementación del Modelo. Veremos algunas posibilidades.Una solución muy simple aunque bastante cándida e inocentona, basada en la primera de las implementaciones del Modelo que hemos visto anteriormente, podría ser la siguiente:
var personas = _services.ObtenerPaginaDePersonas(
page,
personasPorPagina,
"it." + sort + " " + sortDir
);
Como podemos observar, construimos directamente la expresión de ordenación en Entity SQL basándonos en los parámetros recibidos en el query string. Obviamente, y dado que estos parámetros son fácilmente manipulables, un nombre de columna incorrecta o una dirección distinta de “ASC” o “DESC” provocarían una excepción en tiempo de ejecución:Este error es fácil de evitar simplemente introduciendo código que compruebe estos parámetros y establezca valores por defecto en caso de existir algún problema, como el mostrado a continuación, donde comprobamos que la columna de ordenación se encuentra en una “lista blanca” de columnas permitidas, y que la ordenación es ascendente o descendente:
...
sortDir = sortDir.Equals("desc", StringComparison.CurrentCultureIgnoreCase) ? sortDir : "asc";
var validColumns = new[] { "apellidos", "fechanacimiento", "email", "numerodehijos" };
if (!validColumns.Any(c => c.Equals(sort, StringComparison.CurrentCultureIgnoreCase)))
sort = "apellidos";
var personas = _services.ObtenerPaginaDePersonas(
page,
personasPorPagina,
"it." + sort + " " + sortDir
);
...
Si en cambio preferimos utilizar en el Modelo la opción que vimos anteriormente basada en el uso de árboles de expresión, también podemos crear un código que suministre los criterios de ordenación en función de la columna y dirección actual:IEnumerable<Persona> personas;
Direccion dir = sortDir.Equals("ASC", StringComparison.CurrentCultureIgnoreCase) ?
Direccion.Ascendente:
Direccion.Descendente;
switch(sort.ToLower())
{
case "fechanacimiento":
personas = _services.ObtenerPaginaDePersonas(page, personasPorPagina, p => p.FechaNacimiento, dir);
break;
case "numerodehijos":
personas = _services.ObtenerPaginaDePersonas(page, personasPorPagina, p => p.NumeroDeHijos, dir);
break;
case "email":
personas = _services.ObtenerPaginaDePersonas(page, personasPorPagina, p => p.EMail, dir);
break;
case "apellidos":
personas = _services.ObtenerPaginaDePersonas(page, personasPorPagina, p => p.Apellidos, dir);
break;
default:
personas = _services.ObtenerPaginaDePersonas(page, personasPorPagina, p => p.Apellidos, dir);
break;
}
En el proyecto que podréis descargar desde el enlace disponible al final del post, encontraréis ambas implementaciones completas.Antes de pasar a la Vista, veamos el código completo del controlador pasando los criterios de ordenación al Modelo mediante cadenas. Exceptuando las líneas introducidas para mejorar la legibilidad del código, apenas llegamos a la docena de líneas para completar la acción:
public class Demo2Controller : Controller
{
private ModelServices _services = new ModelServices();
public ActionResult Index(int page = 1, string sort = "Apellidos", string sortDir = "ASC")
{
const int personasPorPagina = 10;
var numPersonas = _services.ContarPersonas();
sortDir = sortDir.Equals("desc", StringComparison.CurrentCultureIgnoreCase) ? sortDir : "asc";
var validColumns = new[] { "apellidos", "fechanacimiento", "email", "numerodehijos" };
if (!validColumns.Any(c => c.Equals(sort, StringComparison.CurrentCultureIgnoreCase)))
sort = "apellidos";
var personas = _services.ObtenerPaginaDePersonas(
page,
personasPorPagina,
"it." + sort + " " + sortDir
);
var datos = new PaginaDePersonasViewModel()
{
NumeroDePersonas = numPersonas,
PersonasPorPagina = personasPorPagina,
Personas = personas
};
return View(datos);
}
}
3. La Vista
La mayor parte de la Vista que implementamos en el post anterior sigue siendo válida, sólo tendremos que retocarle un par de detalles que encontramos en las primeras cuatro líneas.@model WebGridDemo.ViewModels.PaginaDePersonasViewModel
@{
ViewBag.Title = "Personas";
WebGrid grid = new WebGrid(rowsPerPage: Model.PersonasPorPagina);
grid.Bind(Model.Personas, autoSortAndPage: false, rowCount: Model.NumeroDePersonas); }
En la primera línea, en la directiva @model debemos definir el tipo de datos recibidos desde el Controlador, que como ya hemos visto, en este caso se trata de una instancia de PaginaDePersonasViewModel
que contiene toda la información que necesita la Vista para maquetarse apropiadamente.A continuación, tras establecer el título de la página, instanciamos el WebGrid suministrándole a su constructor el valor del parámetro
rowsPerPage
, el número de filas por página que estamos utilizando, que es el mismo usado por el Controlador a la hora de solicitar los datos al Modelo.Finalmente, invocamos a su método
Bind()
, donde podemos indicamos:- el conjunto de Personas a mostrar en la página actual,
- que no deseamos utilizar la ordenación y paginación automática, estableciendo
autoSortAndPage
afalse
, - mediante el parámetro
rowCount
, el número total de personas almacenadas en la base de datos.
4. Y recapitulando…
A lo largo de este post hemos vuelto a recorrer el Modelo, Controlador y Vista que ya implementamos hace unos días, comentando los cambios que tenemos que hacer para conseguir una paginación de datos más eficiente.Hemos visto distintas alternativas para implementar el Modelo, basadas en Entity Framework. No son las únicas posibilidades ni mucho menos, pero espero al menos que os den ideas para implementar vuestras propias soluciones. También hemos reescrito el Controlador, implementando el consumo de estos servicios del Modelo para obtener justo la información que necesitamos para mostrar una página de datos, y la hemos pasado a la Vista, a la que hemos tenido que hacer sólo ligeras modificaciones.
Finalmente, como el post ha quedado algo extenso, repasamos rápidamente qué es necesario hacer para implementar un WebGrid con una paginación eficiente:
- En el Modelo:
- Necesitamos un mecanismo para obtener el número total de elementos.
- Asimismo, tendremos que crear un método para obtener los elementos pertenecientes a una página concreta, atendiendo a los criterios de ordenación suministrados.
- En el Controlador:
- Debemos capturar los parámetros
page
,sort
ysortDir
de la petición, a través de los cuales podremos conocer la página a mostrar, la columna y dirección de ordenación, respectivamente. - Obtener desde el Modelo el número total de elementos, así como el conjunto de instancias pertenecientes a la página actual de datos, según los criterios de ordenación actuales.
- Retornar la vista, suministrándole:
- Los datos a mostrar.
- El total de elementos.
- El número de elementos por página que estamos usando
- En la Vista:
- Indicar, al instanciar el WebGrid, el número de elementos por página (dato enviado desde el Controlador).
- Enlazar en WebGrid a la colección suministrada por el Controlador, desactivando la paginación y ordenación automática, e indicándole el total de elementos disponibles.
Como en otras ocasiones, podéis descargar el proyecto desde SkyDrive (requiere VS2010, SQL Express y ASP.NET MVC 3). ¡Que aproveche! ;-)
Publicado en: Variable not found.
martes, 1 de febrero de 2011
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.
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.
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:
Y de momento, he aquí todo el Modelo que necesitamos de momento.
Observad el siguiente código, una vista Razor que recibe una enumeración de objetos
El resultado en ejecución lo podemos ver en la siguiente captura de pantalla:
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.
Aunque hay otras formas de hacerlo, habitualmente encontraremos en el parámetro
El resultado de la ejecución del código utilizando este último código sería:
Algo ha mejorado la cosa, aunque todavía tenemos que afinar algo más en cuanto a la presentación.
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
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
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:
En ejecución ya sí que podemos ver algo más terminado:
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
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:
Y el resultado, como el siguiente:
Asimismo, el propio constructor de WebGrid permite modificar también numerosos aspectos funcionales del grid mediante los siguientes parámetros:
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
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.
Ah, podéis descargar el proyecto de demostración desde mi Skydrive (requiere VS2010, MVC 3 y SQL Express).
Publicado en: Variable not found.
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
Lo 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.
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 llamaremosPersonasController
, 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:
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étodoGetHtml()
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:
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 columnasColumn()
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.
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:
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:
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 atrue
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ónWebGridPagerModes
: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 quemode
contenga el valorWebGridPagerModes.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 “>>”.
...
<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
})
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.
Ah, podéis descargar el proyecto de demostración desde mi Skydrive (requiere VS2010, MVC 3 y SQL Express).
Publicado en: Variable not found.
miércoles, 26 de enero de 2011
Desde la primera versión de ASP.NET MVC se permite añadir a la mayoría de los helpers que generan etiquetas HTML atributos personalizados. Para ello, se introducían objetos anónimos cuyas propiedades eran transformadas a atributos en el momento del renderizado, como en el siguiente ejemplo:
Este código generaría el siguiente marcado:
Ahora que se van a poner muy de moda los atributos personalizados al estilo HTML5, como los “data-xxxx”, nos encontramos con un pequeño problema a la hora de introducirlos en objetos anónimos: los lenguajes como C# o VB no permiten el uso de guiones en los nombres de propiedades, por lo que si escribimos el siguiente código, se generará un error de compilación:
En ASP.NET MVC 3, por convención, es posible introducir este tipo de propiedades, pero utilizando el guión bajo (“_”) en lugar del guión medio (“-“). O en otras palabras, si queremos que el helper genere un atributo data-xx en el marcado HTML, debemos nombrar la propiedad del objeto anónimo como data_xx, tal y como se hace en el siguiente ejemplo:
… que generaría el siguiente marcado, correcto en HTML5:
Publicado en: Variable not found.
Este código generaría el siguiente marcado:
Ahora que se van a poner muy de moda los atributos personalizados al estilo HTML5, como los “data-xxxx”, nos encontramos con un pequeño problema a la hora de introducirlos en objetos anónimos: los lenguajes como C# o VB no permiten el uso de guiones en los nombres de propiedades, por lo que si escribimos el siguiente código, se generará un error de compilación:
En ASP.NET MVC 3, por convención, es posible introducir este tipo de propiedades, pero utilizando el guión bajo (“_”) en lugar del guión medio (“-“). O en otras palabras, si queremos que el helper genere un atributo data-xx en el marcado HTML, debemos nombrar la propiedad del objeto anónimo como data_xx, tal y como se hace en el siguiente ejemplo:
… que generaría el siguiente marcado, correcto en HTML5:
Publicado en: Variable not found.
miércoles, 19 de enero de 2011
En el nuevo ASP.NET MVC 3 me he topado con dos helpers que no conocía y que, a pesar de su sencillez, en ocasiones pueden resultar bastante interesantes:
ObjectInfo
El primero de ellos puede sernos útil cuando queremos volcar el contenido completo de un objeto sobre la página, por ejemplo cuando estemos depurando. La forma general de utilización es la siguiente:
Los parámetros son:
El resultado será un contenido HTML maquetado de forma que pueda apreciarse muy claramente el contenido del objeto. En la siguiente captura de pantalla se observa el resultado, teniendo en cuenta que hemos enviado al helper un objeto que contiene un enumerable de Personas :
ServerInfo
Por otra parte, el helper
Su único método, que además no tiene parámetros, retorna el string HTML listo para ser incluido en la salida, por ejemplo así (en Razor):
Y el resultado sería como el que sigue:
Publicado en: Variable not found.
ObjectInfo
y ServerInfo
. Vamos a verlos muy rápidamente.ObjectInfo
El primero de ellos puede sernos útil cuando queremos volcar el contenido completo de un objeto sobre la página, por ejemplo cuando estemos depurando. La forma general de utilización es la siguiente:
ObjectInfo.Print(objeto, profundidad, elementos_enumeracion)
objeto
, que es el objeto que queremos volcar.profundidad
, el número de niveles en caso de tratarse de objetos cuyas propiedades son, a su vez, otros objetos.elementos_enumeracion
, el número de elementos mostrados cuando se trata de colecciones.
<%: ObjectInfo.Print(Model, 2, 5) %>
El resultado será un contenido HTML maquetado de forma que pueda apreciarse muy claramente el contenido del objeto. En la siguiente captura de pantalla se observa el resultado, teniendo en cuenta que hemos enviado al helper un objeto que contiene un enumerable de Personas :
ServerInfo
Por otra parte, el helper
ServerInfo
muestra información sobre el servidor en el que se está ejecutando la aplicación: configuración y variables de servidor, información del runtime y variables de entorno.Su único método, que además no tiene parámetros, retorna el string HTML listo para ser incluido en la salida, por ejemplo así (en Razor):
@ServerInfo.GetHtml()
Publicado en: Variable not found.
martes, 21 de diciembre de 2010
Hace no demasiado tiempo escribía sobre las formas de evitar el error “Request.Form peligroso”, mostrando distintas técnicas para controlar la forma en que ASP.NET defiende nuestras aplicaciones de ataques de inyección de script evitando la entrada de campos de formulario, cookies o parámetros del querystring con contenidos HTML. En ese post cubría hasta la versión 2 de MVC, Webmatrix, y ASP.NET Webforms.
Ahora, por fin la RC 2 de ASP.NET MVC 3 estabiliza por fin algo que ha sufrido varios cambios durante toda la fase de pruebas del producto, y que nos estaba volviendo algo locos por el cambio de denominación de atributos, o la aparición y desaparición de propiedades.
En ASP.NET MVC 3, por defecto, todos los parámetros de entrada serán sometidos a las comprobaciones oportunas para evitar la aparición de etiquetas y entidades HTML. En caso de detectar la aparición de algún texto peligroso, aparecerá el mismo error de siempre:
Sin embargo, a diferencia de versiones anteriores, la desactivación de la validación se puede realizar ahora de forma mucho más detallada, únicamente sobre los campos que nos interesen, evitando así los problemas derivados del enfoque previo, según el cual se validaban todos los campos o no se validaba ninguno.
Para ello, se ha introducido el atributo
Un aspecto importante es que con MVC 3 ya no será necesario introducir el atributo
Y por otra parte, comentar que desde la RC de MVC 3 no se acepta el parámetro
Todo lo que he dicho anteriormente es verdad, salvo por un pequeño detalle. Debido a un bug de la Release Candidate 2 de ASP.NET MVC 3, ya comentado por ScottGu hace unos días, el atributo
Obviamente, la versión final de MVC 3 vendrá con este problemilla solucionado.
Publicado en: Variable not found.
Ahora, por fin la RC 2 de ASP.NET MVC 3 estabiliza por fin algo que ha sufrido varios cambios durante toda la fase de pruebas del producto, y que nos estaba volviendo algo locos por el cambio de denominación de atributos, o la aparición y desaparición de propiedades.
En ASP.NET MVC 3, por defecto, todos los parámetros de entrada serán sometidos a las comprobaciones oportunas para evitar la aparición de etiquetas y entidades HTML. En caso de detectar la aparición de algún texto peligroso, aparecerá el mismo error de siempre:
Sin embargo, a diferencia de versiones anteriores, la desactivación de la validación se puede realizar ahora de forma mucho más detallada, únicamente sobre los campos que nos interesen, evitando así los problemas derivados del enfoque previo, según el cual se validaban todos los campos o no se validaba ninguno.
Para ello, se ha introducido el atributo
[AllowHtml]
(definido en System.Web.Mvc
), que aplicado sobre una propiedad del modelo, permite que ésta contenga etiquetas y otros elementos que podrían ser considerados peligrosos durante el proceso de binding. El siguiente ejemplo muestra una entidad que permitiría contenido HTML en la propiedad Texto
, pero no en el Titulo
:Un aspecto importante es que con MVC 3 ya no será necesario introducir el atributo
requestValidationMode="2.0"
en el elemento <httpRuntime>
del web.config
, ni ningún otro cambio en el archivo de configuración. Simplemente, el anterior código funcionará :-)Y por otra parte, comentar que desde la RC de MVC 3 no se acepta el parámetro
Exclude
del atributo [ValidateInput]
, que se utilizaba para indicar a nivel de controlador las propiedades o campos a omitir del proceso de validación. Por esta razón, dicho atributo será posible utilizarlo únicamente como siempre, para activar o desactivar la validación a nivel de acción, es decir, para todos los valores de la entrada; por ejemplo, en el siguiente código, se indica que los parámetros de entrada a la acción Edit
no deben ser validados:Hey, todo esto está muy bien, pero… ¡AllowHtml no me funciona!
Ya, pero que no cunda el pánico ;-DTodo lo que he dicho anteriormente es verdad, salvo por un pequeño detalle. Debido a un bug de la Release Candidate 2 de ASP.NET MVC 3, ya comentado por ScottGu hace unos días, el atributo
[AllowHtml]
no funcionará directamente a no ser que introduzcamos el siguiente código en el global.asax:Obviamente, la versión final de MVC 3 vendrá con este problemilla solucionado.
Publicado en: Variable not found.