ASP .NET MVC - Filtrando registros com Dropdownlist


Se você pretende desenvolver páginas web dinâmicas usando a tecnologia ASP .NET MVC provavelmente vai se deparar com a tarefa de ter que exibir uma lista de itens selecionáveis em uma página e de realizar operações para filtrar registros conforme uma condição.

Uma lista de itens selecionáveis é obtida usando o controle DropDownList que na verdade é renderizado como um elemento Select da HTML que usa a tag <select></select>.

É uma tarefa simples mas se você procurar na web sobre o assunto pode ficar um pouco confuso com as informações obtidas se estiver começando agora com a tecnologia ASP .NET MVC ou se você for usuário dos Web Forms da ASP .NET.

Uma das causas da confusão é que esta tarefa pode ser feito de diversas formas e isso pode variar dependendo do tipo de projeto (Usando JQuery, Json, etc.), do tipo engine usado no projeto ( .ASPX ou Razor), da origem dos itens (arquivo XML, arquivo Texto, Banco de dados, lista de itens, etc.) e qual a linguagem usada (C# ou VB .NET)

Este artigo procura ser bem direto e objetivo mostrando apenas o que é necessário para filtrar registros usando o controle DropDownList na tecnologia ASP .NET MVC.

Ferramentas utilizadas

Filtrando registros com ASP .NET MVC

Abra o VS 2012 Express for Web, clique em New Project e selecione o template Visual C# -> Web -> ASP .NET MVC 4 Web Application;

Informe o nome Mvc_FiltrarRegistros e escolha o local onde você deseja salvar o projeto (Location) informando também o nome da solução e clique em OK;

A seguir selecione o template Basic e o engine Razor; com isso será criado um projeto com uma estrutura MVC básica (ver figura abaixo) que iremos usar a alterar conforme nossas pretensões;

Será criado o projeto completo com a estrutura das pastas padrão de um projeto ASP .NET MVC.

Podemos observar no projeto asa seguintes pastas :

Não precisa ser muito esperto para perceber que estes diretórios contém os arquivos que irão implementar o MVC - Model , View e o Controller. (Modelo, Visão e Controlador)

Definindo o Model

Vamos iniciar definindo o nosso Model que será representando pelas entidades geradas pelo Entity Framework com o banco de dados Northwind.mdf.

Vamos criar um Entity data Model clicando com o botão direito sobre a pasta Models e selecionando Add New Item;

A seguir selecione o template Data-> ADO .NET Entity Data Model e informe o nome Northwin.edmx e clique em Add;

Na janela Choose Model Contents selecione : Generate from database e clique em Next>;

Selecione a conexão com o banco de dados Northwind(Se ela não existir clique em New Connection e crie a conexão), e informe o nome NorthwindEntities deixando a opção Save Entity connection settings in Web.Config marcada e clique em Next>;

Selecione todas as tabelas ; marque as caixas de seleção, aceite o nome para o namespace Model e clique em Finish;

O arquivo Northwind.edmx será gerado e poderá ser visualizado exibindo o modelo de entidades relacionadas que foi obtido a partir das tabelas do banco de dados Northwind.mdf;

Selecionando o modelo podemos ver na janela Properties (pressione F4) que o nome do nosso container é NorthwindEntities;

Usando o Model

Nosso próximo passo seria criar um controlador clicando com o botão direito do mouse sobre a pasta Controllers, selecionar a opção Add Controller e definindo as configurações conforme a figura abaixo:

Ao clicar no botão Add seria criado o controlador CustomersController.cs na pasta Controllers e todas as Views na pasta Views->Customers conforme mostra a figura abaixo:

Se você executar a aplicação neste momento vai encontrar um erro do tipo: Não é possível encontrar o recurso.

Precisamos alterar a rota que foi definida para o Controller no arquivo RouteConfig da pasta App_Start .

O valor padrão esta assim : controller="Home" e temos que alterar para controller="Customers" conforme mostrado abaixo:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace Mvc_FiltrarRegistros
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Customers", action = "Index", id = UrlParameter.Optional }
            );
        }
    }
}

Após isso vamos ajustar a view Index.cshtml que foi gerada na pasta Views/Customers alterando o seu código para que ela exiba apenas algumas informações dos clientes e não todas como padrão. Abaixo temos o código alterado da view Index.cshtml:

@model IEnumerable<Mvc_FiltrarRegistros.Models.Customer>
@{
    ViewBag.Title = "Index";
}
<h2>Clientes </h2>
<table>
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.CompanyName) </th>
        <th>@Html.DisplayNameFor(model => model.ContactName) </th>
        <th>@Html.DisplayNameFor(model => model.City) </th>
        <th> @Html.DisplayNameFor(model => model.Country)</th>
        <th> @Html.DisplayNameFor(model => model.Phone) </th>
        <th></th>
    </tr>
    @foreach (var item in Model) {
    <tr>
        <td>@Html.DisplayFor(modelItem => item.CompanyName) </td>
         <td> @Html.DisplayFor(modelItem => item.ContactName) </td>
        <td> @Html.DisplayFor(modelItem => item.City) </td>
        <td> @Html.DisplayFor(modelItem => item.Country) </td>
        <td>  @Html.DisplayFor(modelItem => item.Phone) </td>
        <td>
            @Html.ActionLink("Edit", "Edit", new { id=item.CustomerID }) |
            @Html.ActionLink("Details", "Details", new { id=item.CustomerID }) |
            @Html.ActionLink("Delete", "Delete", new { id=item.CustomerID })
        </td>
    </tr>
}

Agora executando o projeto teremos o resultado abaixo:

As informações são obtidas pela Action Index do controlador e repassada à respectiva view Index.cshtml. O código atual da Action Index do nosso controlador CustomerController é :

using System.Data;
using System.Linq;
using System.Web.Mvc;
using Mvc_FiltrarRegistros.Models;
namespace Mvc_FiltrarRegistros.Controllers
{
    public class CustomersController : Controller
    {
        private NorthwindEntities db = new NorthwindEntities();
        //
        // GET: /Customers/
        public ActionResult Index()
        {
            return View(db.Customers.ToList());
        }

       .....
       .....

    }
}

Vamos alterar o código da Action Index() conforme abaixo :

 public ActionResult Index()
 {
         var model = from c in db.Customers
                           orderby c.ContactName
                           select c;
            return View(model);
}

Se executarmos novamente a aplicação a única diferença e que teremos uma relação de clientes ordenados pelo campo ContactName.

Vamos então permitir que o cliente filtre os registros por pais(country) alterando a nossa Action e incluindo uma string de consulta usando a cláusula Where para filtrar pelo campo Country. Veja como ficou a Action Index() do controlador:

        //
        // GET: /Customers/
        public ActionResult Index(string pais)
        {
            var model = from c in db.Customers
                            orderby c.ContactName
                            where c.Country == pais
                            select c;
            return View(model);
        }

Executando o projeto novamente após essa alteração teremos o seguinte resultado:

Esse resultado era previsto pois a action Index realiza a consulta com base em uma query string que deverá ser informada contendo o nome do país para poder realizar o filtro. Como na primeira execução chamamos diretamente a action Index sem passar parâmetro algum o resultado não retornar nenhuma informação.

Vamos então passar via URL a query string informando o país igual a Brazil, fazendo isso iremos obter:

Note que informamos na URL a string de consulta na forma: customers?pais=Brazil

Alterando a consulta na action Index conforme abaixo, se nada for informado na URL agora serão retornados todos os registros:

        //
        // GET: /Customers/
        public ActionResult Index(string pais)
        {
            var model = from c in db.Customers
                        orderby c.ContactName
                        where c.Country == pais || pais.Equals(null) || pais.Equals("")
                        select c;
            return View(model);
        }

E se quisermos filtrar por pais e por cidade (campos Country e City) ?

Para isso basta alterar a consulta conforme a abaixo incluindo mais uma cláusula Where que filtra pelo campo City:

        //
        // GET: /Customers/
        public ActionResult Index(string pais, string cidade)
        {
            var model = from c in db.Customers
                        orderby c.ContactName
                        where c.Country == pais || pais.Equals(null) || pais.Equals("")
                        where c.City == cidade || cidade.Equals(null) || cidade.Equals("")
                        select c;
            return View(model);
        }

Agora informando o pais e a cidade na URL iremos obter para pais=Brazil e cidade=Rio de Janeiro o seguinte resultado:

A string de consulta usada na URL foi : customers?pais=Brazil&cidade=Rio de Janeiro

Regras padrão para mapeamento das URLs

Um método Action possui um mapeamento um-para-um com uma interação do usuário como : clicar um link, informar um url no navegador e submeter um formulário;

Cada uma dessas interações do usuário resulta em uma requisição que é enviada ao servidor, sendo que em cada caso a URL da requisição inclui informação que o framework MVC usa para invocar um método Action;

Assim, quando um usuário informa uma URL no navegador , a aplicação MVC usa as regras de roteamento que são definidas no arquivo Global.asax para realizar o parse da URL e determinar o caminho do controller e o controller então determina o método Action apropriado para tratar a requisição.

Por padrão a URL de uma requisição é tratada como um sub-caminho que inclui o nome do controller seguido pelo nome da Action(ação). Exemplo:

 - Se um usuário informar a URL : http://macoratti.com/Vendas/Produtos/Categorias  o sub-caminho é /Produtos/Categorias;

 - As regras de roteamento tratam "Produtos" como o nome do controller e "Categorias" como o nome da Action;

 - Desta forma a regra de roteamento invoca o método Categorias do controller Produtos a fim de processar a requisição;

 - Se a URL terminar com /Produtos/Detalhes/5 o roteamento padrão trata "Detalhes" como sendo o nome da Action e o método Detalhes do controller Produtos será invocado para processar a requisição;

  - O valor 5 na URL será passado para o método "Detalhes" como um parâmetro;

Com base nisso vamos tomar mais amigável a nossa interface e realizar o filtro por pais e cidade usando controles dropdownlist.

Filtrando com DropDownList

Para fazer isso vamos modificar a nossa Action Index no controlador e utilizar a propriedade ViewBag que é usada para persistir dados entre o Controlador e a View.

Nota:  Lembrem-se que segundo o padrão MVC, o controlador é o responsável por acessar o modelo e obter os dados para enviá-los para a view. A view deve apenas exibir os dados (ou solicitar novos dados). Os dados que o modelo fornece controlador podem vir de qualquer fonte de dados( banco de dados, xml, arquivo texto, etc.)

A propriedade ViewBag é um dicionário chave/valor que usa os recursos dos tipos dinâmicos da linguagem C# sendo um objeto dinâmico onde podemos adicionar propriedades (no controller) e  ler essas propriedades posteriormente(na view). Podemos definir propriedades da seguinte forma ViewBag.chave=valor.

ViewBag.Nome = "Jose Carlos Macoratti";
ViewBag.Data = new DateTime(2013, 10, 10);

Lembrando que o tempo de vida da ViewBag dura apenas entre o envio através do controller e a exibição na View após isso ela fica nula novamente, então se houver um redirect a viewbag se tornará nula.

Nota: A propriedade ViewData também pode ser usada para os mesmos propósitos sendo um dicionário de objetos . Ex: ViewData["Nome"] = "Macoratti"; (O ViewBag esta disponível a partir da ASP .NET MVC 3; já o ViewData existe desde a primeira versão.)

  //
        // GET: /Customers/
        public ActionResult Index(string pais,string cidade)
        {
            ViewBag.Country = (from c in db.Customers
                               select c.Country).Distinct();
            ViewBag.City = (from c in db.Customers
                            select c.City).Distinct();
            
            var model = from c in db.Customers
                              orderby c.ContactName
                              where c.Country == pais || pais.Equals(null) || pais.Equals("")
                              where c.City == cidade || cidade.Equals(null) || cidade.Equals("")
                              select c;
            return View(model);
        }

Agora basta alterar o código da view Index.cshtml criando os controles dropdownlist conforme abaixo:

@model IEnumerable<Mvc_FiltrarRegistros.Models.Customer>
@{
    ViewBag.Title = "Index";
}
<h2>Clientes </h2>
<p>
    @using (Html.BeginForm())
    {
        <text>Pais</text>@Html.DropDownList("pais", new SelectList(ViewBag.Country))
        <text>Cidade</text>@Html.DropDownList("cidade", new SelectList(ViewBag.City))
        <input type="submit" value="Procurar" />
    }
</p>
...
...

O método Html.BeginForm é um HTML Helper que gera um elemento de formulário HTML configurado para um postback para o método action. Uma vez que não se passaram os parâmetros para o método auxiliar, ele assume que queremos dar um postback para a mesma URL. Um truque é usar isso em uma declaração using :

@using (Html.BeginForm()) {
   ...conteúdo do formulario...
   }

Normalmente a instrução using garante que um objeto é descartado quando sai do escopo. Ele é usado para conexões de bancos de dados, por exemplo, para se certificar de que elas serão fechadas assim que uma consulta for concluída.

Em vez de descartar um objeto, o Helper Html.BeginForm fecha o elemento de formulário HTML quando sai do escopo. Isto significa que o método Html.BeginForm cria ambas as partes de um elemento de formulário :

<form action="/Home/RsvpForm" method="post">
...conteudo do formulario...
</form>

Executando o projeto novamente agora teremos:


Note que como estamos dando um POST a informação não aparece na URL. Para permitir a marcação podemos usar uma sobrecarga do Html.BeginForm e definir o atributo FormMethod.Get que fará com que o formulário seja processando usando o GET. Veja como ficou a alteração no código abaixo:
 
@model IEnumerable<Mvc_FiltrarRegistros.Models.Customer>
@{
    ViewBag.Title = "Index";
}
<h2>Clientes </h2>
<p>
    @using (Html.BeginForm(("Index","Customers",FormMethod.Get))
    {
        <text>Pais</text>@Html.DropDownList("pais", new SelectList(ViewBag.Country))
        <text>Cidade</text>@Html.DropDownList("cidade", new SelectList(ViewBag.City))
        <input type="submit" value="Procurar" />
    }
</p>
...
...

Executando o projeto novamente agora teremos:

Observe que a string de consulta agora esta visível na URL devido ao método GET.

GET
Quando um usuário informa os dados nos campos de um formulário e o submete, os dados contidos em cada campo do formulário são transferidos para o servidor. Se o formulário estiver usando o método GET, os dados são anexados à URL como uma string de argumento.
 
Quanto maior a quantidade de campos e quanto mais longas forem as strings de texto informadas , maior será o tamanho da URL e mais complexo será o seu processamento. Sem mencionar que existe um limite – mais ou menos 2500 caracteres - para o número de caracteres que podemos anexar a uma URL.
 
POST
Quando o formulário utilizar o método POST os dados são enviados no corpo da solicitação HTTP e não são adicionados á URL tornando mais fácil o processamento da solicitação e melhorando o desempenho do servidor.
 
Em geral deve-se usar POST quando os formulários forem alterar algo no ambiente da aplicação e GET para realizar pesquisas.

Pegue o projeto completo aqui:  Mvc_FiltrarRegistros.zip (sem as referências)

Rom 12:20 Antes, se o teu inimigo tiver fome, dá-lhe de comer; se tiver sede, dá-lhe de beber; porque, fazendo isto amontoarás brasas de fogo sobre a sua cabeça.

Rom 12:21 Não te deixes vencer do mal, mas vence o mal com o bem.

        Gostou ?   Compartilhe no Facebook   Compartilhe no Twitter

Referências:


José Carlos Macoratti