El problema fundamental es que WebGrid no incorpora ningún tipo de ayuda para realizar esta tarea tan frecuente; simplemente se encarga de mostrar los datos que le suministremos, por lo que si queremos filtrarlos debemos hacerlo de forma manual. En pura teoría bastaría con hacer llegar a Controlador los filtros a aplicar, y que éste consultara la información desde el Modelo teniendo en cuenta los criterios establecidos.
Sin embargo, algo que puede parecer relativamente simple se complica un poco si queremos, además, mantener intacta la capacidad de ordenación y paginación de datos, puesto que debemos encargarnos también del mantenimiento del estado entre peticiones. Pero vaya, no es nada que no podamos solucionar en unos minutos 😉
Partiendo del proyecto que desarrollamos en el último post de la serie vamos a ver cómo podemos añadir al grid un par de criterios de búsqueda, de forma que el resultado luzca tal que así:
El campo Texto nos permitirá buscar subcadenas en el nombre y apellidos de las personas almacenadas en la base de datos, y los otros dos campos permitirán filtrar por rango (mín-máx) los hijos que tienen. Como es habitual, estas condiciones las combinaremos con un Y lógico.
1. La vista
El código es el siguiente:
@using(Html.BeginForm(null, null, FormMethod.Get))
{
<fieldset>
<legend>Criterios de búsqueda</legend>
@Html.Label("buscar", "Texto:")
@Html.TextBox("buscar")
@Html.Label("hijosMin", "Hijos mín:")
@Html.TextBox("minHijos")
@Html.Label("maxHijos", "Hijos máx:")
@Html.TextBox("maxHijos" )
<input type="submit" value="Buscar" />
</fieldset>
}
De esta forma podemos propagar muy fácilmente el valor de los controles (textbox) entre las llamadas:
- si el usuario introduce criterios y pulsa el botón enviar, la URL a la que se realizará la petición será, por ejemplo,
/personas/index?buscar=aguilar&hijosMin=0&hijosMax=8
. - si el usuario utiliza los botones de navegación propios del grid (avance/retroceso de página, salto a página directa, o reordenación), estos parámetros serán añadidos a los anteriores, de forma que seguirán manteniendo sus valores entre las distintas llamadas. Esto es así gracias a que Webgrid genera los enlaces de estas acciones respetando los parámetros actuales del query string; es decir, si estamos filtrando y saltamos a la página 4, accederemos a una dirección que incluirá tanto la información de los criterios de búsqueda como la de paginación, algo como:
/personas/index?buscar=aguilar&minHijos=0&maxHijos=8&page=4
.
2. El Controlador
Dado que ahora necesitará también obtener los criterios de ordenación, debemos ampliar su definición añadiendo parámetros para estos valores:
public ActionResult Index(int page = 1, string sort = "Apellidos", string sortDir = "ASC",
string buscar = null, int? minHijos = null, int? maxHijos = null)
¿Y en qué puntos necesitamos utilizar estos nuevos parámetros? Pues sólo en dos:
- en la llamada que hacemos al Modelo para contar el total de filas del grid, a la que tendremos que informar sobre los criterios de filtrado para que el conteo se realice correctamente.
- en la llamada que hacemos al Modelo para obtener las filas a mostrar en la página actual, donde obviamente también deberemos tener en cuenta los filtros.
public ActionResult Index(int page = 1, string sort = "Apellidos", string sortDir = "ASC",
string buscar = null, int? minHijos = null, int? maxHijos = null)
{
var numPersonas = _services.ContarPersonas(buscar, minHijos, maxHijos);
var personas = _services.ObtenerPaginaDePersonasFiltrada(page, PERSONAS_POR_PAGINA,
sort, sortDir, buscar, minHijos, maxHijos);
var datos = new PaginaDePersonasViewModel()
{
NumeroDePersonas = numPersonas,
PersonasPorPagina = PERSONAS_POR_PAGINA,
Personas = personas
};
return View(datos);
}
3. El Modelo
ContarPersonas
y ObtenerPaginaDePersonasFiltrada
) tengan en cuenta los parámetros en los que indicamos las condiciones de búsqueda.
En el primero de ellos, simplemente retornamos el número de personas que cumplan los criterios que nos llegan como parámetros:
public int ContarPersonas(string textoBuscar = null, int? minHijos = null, int?
maxHijos = null)
{
IQueryable<Persona> query = _datos.Personas;
query = queryPersonasFiltrada(textoBuscar, minHijos, maxHijos, query);
return query.Count();
}
queryPersonasFiltrada()
que estamos utilizando únicamente se encarga de añadir a la query las cláusulas where
que necesitamos para tener en cuenta las condiciones especificadas:
private static IQueryable<Persona> queryPersonasFiltrada(string textoBuscar, int? minHijos,
int? maxHijos, IQueryable<Persona> query)
{
if (!string.IsNullOrWhiteSpace(textoBuscar))
query = query.Where(p => p.Nombre.Contains(textoBuscar) || p.Apellidos.Contains(textoBuscar));
if (maxHijos != null)
query = query.Where(p => p.NumeroDeHijos <= maxHijos);
if (minHijos != null)
query = query.Where(p => p.NumeroDeHijos >= minHijos);
return query;
}
public IEnumerable<Persona> ObtenerPaginaDePersonasFiltrada(int paginaActual, int personasPorPagina,
string columnaOrdenacion, string sentidoOrdenacion,
string textoBuscar, int? minHijos, int? maxHijos)
{
// Comprobamos los datos de entrada
sentidoOrdenacion = sentidoOrdenacion.Equals("desc", StringComparison.CurrentCultureIgnoreCase) ?
sentidoOrdenacion : "asc";
var validColumns = new[] { "apellidos", "fechanacimiento", "email", "numerodehijos" };
if (!validColumns.Contains(columnaOrdenacion.ToLower()))
columnaOrdenacion = "apellidos";
if (paginaActual < 1) paginaActual = 1;
if (personasPorPagina < 1) personasPorPagina = 10;
// Generamos la consulta
var query = (IQueryable<Persona>) _datos.Personas
.OrderBy("it." + columnaOrdenacion + " " + sentidoOrdenacion);
query = queryPersonasFiltrada(textoBuscar, minHijos, maxHijos, query);
return query
.Skip((paginaActual - 1) * personasPorPagina)
.Take(personasPorPagina)
.ToList();
}
queryPersonasFiltrada()
para aplicar los criterios de consulta.En resumen…
- primero, incluir en la Vista un formulario donde se recojan los criterios de la consulta para enviarlos al controlador.
- segundo, preparar el Controlador para que reciba estos criterios y los haga llegar al Modelo.
- tercero, en el Modelo, simplemente aplicar esos criterios en el momento de contar las filas totales, y en la obtención de los datos a mostrar en la página del grid.
Publicación original (5-oct-2011): http://www.variablenotfound.com/2011/10/aspnet-mvc-webgrid-con-filtro.html