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 ;)

17 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!
miércoles, 5 de octubre de 2011
ASP.NET MVCHace unos días, el amigo Cadavid realizaba una consulta en los comentarios de la serie de posts que escribí hace unos meses sobre el helper Webgrid, sobre cómo podíamos implementar filtros personalizados sobre los datos mostrados, y dado que no es algo que se pueda explicar en un simple comentario, me comprometí a escribir un post sobre el tema, así que ahí va.

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í:

Criterios de filtro con Webgrid
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

En primer lugar introducimos en la vista, justo antes de generar el grid, el formulario que utilizaremos  para solicitar al usuario los criterios de búsqueda:

Cuadro de criterios de búsqueda
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>
}

Observad la simplicidad del formulario. Por no usar, no usa ni siquiera la sintaxis lambda en los helpers de edición. Los controles vamos generarlos partiendo de campos cuyos valores existirán en el query string, y no desde el Modelo, que es lo habitual, y por esta razón fijaos que el formulario está configurado para ser enviado utilizando el método HTTP GET.

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.
Y con esto, dejamos completada la capa vista.

2. El Controlador

Si recordáis, el método de acción encargado de obtener los datos de grid y enviar la vista con los datos al usuario recibía tres parámetros: la página actual, el campo de ordenación y el sentido de ésta (ascendente/descendente).

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)

Observad que son todos parámetros opcionales, y los establecemos a nulo para detectar fácilmente cuándo nos vienen rellenos.

¿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.
La acción quedaría más o menos así:

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);
}

Y esto es todo en el controlador.

3. El Modelo

Y por último, ya en el Modelo, debemos hacer que los métodos utilizados desde el controlador (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();
}

El método de utilidad 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;
}

Por último, implementamos el método que obtiene los datos a mostrar en la página actual:

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();
}

Hay poco que comentar sobre este código. En primer lugar se realiza una comprobación básica de los parámetros de entrada, para a continuación generar la consulta que se realizará sobre la base de datos. Como podéis observar, también se utiliza el método queryPersonasFiltrada() para aplicar los criterios de consulta.

En resumen…

Como hemos podido ver, la implementación de criterios de búsqueda en un WebGrid no difiere mucho de la que habíamos descrito en su momento. Sólo hay que tener en cuenta los siguientes puntos:
  • 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.
En SkyDrive he colgado un proyecto que incluye un ejemplo completo donde podéis ver todo esto en funcionamiento: WebGridFiltradoDemo (MVC 3 + SQL Express).

Publicado en: Variable not found.

13 Comentarios:

CADAVID dijo...

Muchas gracias Jose, Muchos saludos y bendiciones desde Colombia! Sos un Fenómeno!

josé M. Aguilar dijo...

Gracias a tí por participar en variablenotfound!

Un saludo.

Langdon dijo...

Fantastico!! De verdad un trabajo extraordinario.. Gracias por la aportación!! Sigue así.. Saludos.

Jose Luis dijo...

Muchas gracias por el ejemplo, pero como puedo hacer los filtros con la paginación y ordenación que trae por defecto WebGrid y que estos mismos sean tenido en cuenta cuando aplico los filtros?

josé M. Aguilar dijo...

Hola!

en el ejemplo descrito en el post ya se hace. Una vez has seleccionado un filtro, éste se mantiene activo incluso cuando usas los mecanismos de paginación de WebGrid :-)

Saludos

Jose Luis dijo...

Gracias por responder, pero todavía ando un poco confundido.

Lo que quiero decir yo es como hacer los filtros y que se mantengan activos sin necesidad de utilizar esto:

grid.Bind(Model.Personas, autoSortAndPage: false, rowCount: Model.NumeroDePersonas);

Sino algo como simplemente así:

WebGrid grid = new WebGrid(Model)

Es decir, aplicar el filtro pero a la WebGrid de tu primer post :): http://www.variablenotfound.com/2011/02/webgrid-en-mvc-3-paso-paso.html

josé M. Aguilar dijo...

Hola,

Ah, ya te he entendido :-)

Pues es exactamente igual que en el post del webgrid con filtro: creas un formulario HTML que envíe mediante el método GET los parámetros del filtro a la acción, en la acción recoges los parámetros y los envías al modelo para que retorne los datos ya seleccionados.

Te he enviado por mail el proyecto modificado.

Saludos.

Anónimo dijo...

Querido amigo como muchos como yo que nos estamos empapando con esto del mvc3 en primera instancia AGRADECERTE por mostrar la funcionalidad del webgrid y pedirte si puedes poner unos ejemplos del mismo webgrid que mostraste con la aplicacion del dbfisrt, modelfirst y codefirst para ver cual es mejor para aplicar en un proyecto y lo muestres con la misma claridad q mostraste en los mismos y si puedes enviarme a mi correo chelotas@hotmail.com te estare eternamente agradecido :D

Unknown dijo...

Me sale un error :(

var query = (IQueryable)_datos.Clientes.OrderBy("it." + columnaOrdenacion + " " + sentidoOrdenacion);

Los argumentos de tipo para el método 'System.Linq.Queryable.OrderBy(System.Linq.IQueryable, System.Linq.Expressions.Expression>)' no se pueden inferir a partir del uso. Intente especificar los argumentos de tipo explícitamente.

josé M. Aguilar dijo...

Hola!

Tu problema se debe a que en este post (año 2011) se usaba EF 4, donde existía una extensión de IQueryable mediante la cual el método OrderBy aceptaba un string.

A ver si un día tengo tiempo y actualizo estos posts a versiones más recientes de MVC y EF, pero mientras tanto, puedes implementarlo usando tipado fuerte con relativa facilidad. Básicamente, tienes que hacer un switch sobre el campo de ordenación y en función de él aplicar el orderby con sintaxis lambda a la consulta.

Un saludo!

Fernando dijo...

Hola, Enhorabuena buena por los post, son muy interesantes.
Tengo un problema con el grid de Telerik, no puedo poner el total de filas en el pie del grid sin usar paginación.

En mi caso necesito mostrar todos los resultados sin paginar y mostrar el número de filas.
Agradeceria tu ayuda.

Fernando

josé M. Aguilar dijo...

Hola!

Los grids de Telerik son una ciencia en sí mismos ;) Lo mejor que puedo recomendarte es que consultes la documentación de producto y, si no encuentras lo que buscas, uses los canales de soporte como los foros.

Saludos!

Unknown dijo...

Buenas tardes tengo un webgrid algo sencillo ahi me muestra los datos consultados previamente de otro formulario como le puedo hacer para exportar esos datos a excel