ASP.NET MVC veio com a intenção de permitir que o desenvolvimento web se torna-se mais leve e flexível utilizando o padrão de arquitetura MVC (Model View Controller). Este padrão criado em 1979 por Trygve Reenskaug, visa separar a lógica de negócio da lógica de apresentação permitindo um desenvolvimento em camadas isoladas com funções específicas e independentes em cada uma delas. Através do MVC framework temos uma incrível flexibilidade para desenvolver um modelo de aplicação wizard (passo a passo), os velhos conhecidos botões próximo e anterior para quem trabalha com Windows. Desenvolveremos uma aplicação que demonstra um cadastro de produtos que consiste de algumas etapas e uma confirmação dos dados no final utilizando algumas views e chamadas através da funcionalidade RedirectToAction().

Neste exemplo não iremos persistir o objeto no banco de dados, porém será deixado subtendido o lugar para fazê-lo. Talvez em algum artigo futuro possamos desenvolver uma aplicação do início ao fim através das técnicas de MVC com as ferramentas que a Microsoft nos disponibiliza pelo Visual Studio 2010. Nosso exemplo conterá cinco passos conforme o workflow apresentado a seguir.

1. Cadastro de Produto

Cadastro de Produto

2. Definindo o Modelo de Dados

Vamos definir a classe de Modelo e adicione uma classe na pasta /Models. Ela se chamará Produto, com o conteúdo abaixo:


[Serializable]
publicclass Produto
{
    public String Descricao { get;set; }
    public String Categoria { get;set; }
    public String Referencia { get;set; }
    public String FornecedorNome { get;set; }
    public String FornecedorTelefone { get; set; }
    public Decimal Custo { get;set; }
    public Decimal valorVista { get;set; }
    public Decimal valorPrazo { get; set; }
}

Nossa tarefa consiste em criar uma nova instância de Produto todas as vezes que o usuário acessar uma página, preenchendo os campos de acordo com os dados do passo anterior, preservando os dados durante toda a trajetória e finalmente persistindo no último passo. Na linha um utilizamos a marcação [Serializable] que permitirá que os dados se mantenham através da serialização do objeto dentro do formulário através de uma tag HTML input do tipo hidden.

3. Navegação

Para iniciarmos a lógica da navegação entre as páginas, criaremos a classe Controller. Clique com o botão direito do mouse sobre a pasta /Controllers e vá na opção add>Controller e dê o nome de ProdutoController. Não marque a opção para adicionas as ações Create, Update, Delete e Details automaticamente. Preencha a classe com o conteúdo abaixo:


   public class ProdutoController : Controller
    {
        public ActionResult Criar(stringbtnAvancar)
        {
            returnView();
        }
        public ActionResult Fornecedor(stringbtnVoltar, string btnAvancar)
        {
            return View();
        }
    }

O próximo passo será criar a view para a ação Criar(). Será onde o nosso projeto iniciará. Dentro da pasta /Views do projeto crie uma pasta chamada /Produto, isso melhorará a distribuição dos arquivos fontes do projeto. Clique com o botão direito sobre a pasta /Produto escolha a opção Add View. Coloque o nome de Criar, igual à ação da controller. Marque a opção strongly typed e escolha Produto como a classe modelo. Esta view deverá ter o seguinte conteúdo:


<h2>Produtos: Dados Iniciais</h2>
        Preencha os dados abaixo
     <% using (Html.BeginForm())
       { %>
        <%: Html.ValidationSummary() %>
        <%: Html.Serialize("regProduto", Model) %>
        <p>Descrição:<%: Html.EditorFor(x => x.Descricao) %></p>
        <p>Categoria:<%: Html.EditorFor(x => x.Categoria) %></p>
        <p>Referência:<%: Html.EditorFor(x => x.Referencia) %></p>
       
        <p><input type="submit" name="btnAvancar" value="Avançar >" /></p>
     <% } %>

A estrutura de pastas do Solution Explorer deve estar da seguinte forma até o momento:

estrutura das pastas

Antes de testar o nosso trabalho devemos alterar a rota principal da aplicação no arquivo Global.asax alterando a linha que contêm essa instrução:


new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults

Altere o valor do parâmetro controller para Produto e o valor da action para Criar. Salve seu projeto e compile a solução (F5) e você terá o seguinte resultado:

compilação

Vamos desenvolver agora a lógica para desenvolver os próximos passos. Altere o ProdutoController conforme segue abaixo:


public class ProdutoController : Controller
    {
 
       //Este objeto será global para a manipulação de todas as
       //ações da controller
       private Produto regProduto;
 
        public ActionResult Criar(string btnAvancar)
        {
            if (btnAvancar != null)
                return RedirectToAction("Fornecedor");
 
            return View(regProduto);
        }
 
        public ActionResult Fornecedor(string btnVoltar, string btnAvancar)
        {
            if (btnVoltar != null)
                return RedirectToAction("Criar");
            else if (btnAvancar != null)
                return RedirectToAction("Valor");
            else
                return View(regProduto);
        }
     }

Você deve ter percebido que na View o comando Html.BeginForm() não foi especificado a ação de destino. Quando é dado o comando de submit ao clicar no botão Avançar > o browser envia um Request.Form key/value que corresponde ao nome do button. A ação a ser chamada, nesse caso será o mesmo nome da view, por isso é importante que a view tenha o mesmo nome da ação na controller, que em nosso exemplo é Criar. O valor do botão é recebido através de um parâmetro do tipo String com o mesmo nome do botão na view, caso o botão tenha sido clicado, a propriedade value do parâmetro virá com o nome do input como valor, caso contrário o parâmetro virá com o valor null. Dessa forma podemos tratar múltiplos destinos levando em consideração o botão clicado pelo usuário. Agora iremos criar nossa segunda view, conforme foi feita a anterior, porém essa será chamada de Fornecedor, seguindo a mesma ideia de ter o nome da ação da controller. Não se esqueça de marcar a opção strongly typed e selecionar o modelo Produto. A view fornecedor deverá ter o seguinte conteúdo:


       <h2>Produtos: Fornecedor</h2>
        Preencha os dados abaixo
        <% using (Html.BeginForm())
           { %>
        <%: Html.ValidationSummary() %>
        <%: Html.Serialize("regProduto", Model) %>
        <p>Nome Fornecedor:<%: Html.EditorFor(x => x.FornecedorNome) %></p>
        <p>Telefone Fornecedor:<%: Html.EditorFor(x => x.FornecedorTelefone) %></p>
        <p>
            <input type="submit" name="btnVoltar" value="< Voltar" />
            <input type="submit" name="btnAvancar" value="Avançar >" />
        </p>
        <% } %>

Nosso mecanismo de navegação deverá ser executado na seguinte ordem:

mecanismo de navegação

4. Preservando os dados

Até esse momento ao navegar entre uma tela e outra todos os dados digitados se perdem. Para que os dados não se percam é necessário serializarmos dentro do formulário em um campo do tipo hidden utilizando um HTML Helper chamado Html.Serialize().Para utilizar essa opção será necessário referenciar a biblioteca Microsoft.web.mvc que não vem padrão no Visual Studio 2010. Entre no site da Codeplex e faça download do arquivo MVC future correspondente a versão que você está utilizando. Neste exemplo foi utilizado o MVC 3. Após descompactar os arquivos e adicioná-lo a lista de references do projeto. Abra o arquivo web.config e acrescente a referência Microsoft.web.mvc conforme abaixo:


<configuration>
  <system.web>
   <pages>
      <namespaces>
        <!-- Namespaces padrão aqui --> 
        <add namespace="Microsoft.Web.Mvc" />
      </namespaces>
    </pages>
  </system.web>
</configuration>

Adicione na view Criar e Fornecedor a tag Html.Serialize():


<% using (Html.BeginForm())
   { %>
        <%: Html.ValidationSummary() %>
        <%: Html.Serialize("regProduto", Model) %>
       
        <!-- Mantenha o conteúdo abaixo -->
 
<% } %>

O próximo passo é adicionar os métodos OnActionExecuting() e OnResultExecuted() a controller ProdutoController para poder receber os dados referentes ao campo RegProduto e mantê-los entre os redirecionamentos de páginas. A instância RegProduto é salvo em uma coleção TempData. A TempData Assemelha-se muito à Session, porém seu valor dura somente até a próxima requisição HTTP.Confira abaixo os métodos a serem adicionados:


public class ProdutoController : Controller
{
   private Produto regProduto;
   protected override void OnActionExecuting(ActionExecutingContext filterContext)
   {
      var serialized = Request.Form["regProduto"];
      if (serialized != null)
      {
        regProduto = (Produto)new MvcSerializer().Deserialize(serialized, SerializationMode.Signed);
        TryUpdateModel(regProduto);
      }
      else
        regProduto = (Produto)TempData["regProduto"] ?? new Produto();
   }
 
   protected override void OnResultExecuted(ResultExecutedContext filterContext)
   {
        if (filterContext.Result is RedirectToRouteResult)
        {
                TempData["regProduto"] = regProduto;
        }
   }
 
    <!-- Continua com os métodos restantes abaixo -->
}

O método OnActionExecuting() consegue recuperar os valores do formulário, e desserializar a instância regProduto, antes da execução da ação requisitada pela view. Isso através do método mvcSerializer() da biblioteca Microsoft.Web.Mvc. O método OnResultExecuted() consegue serializar a instância regProduto e enviá-la ao formulário de volta a coleção TempData, sempre que houver um retorno da controller, ou seja, sempre que houver retorno de alguma ação da controller. Caso seja necessário acrescentar outros objetos semelhantes ao regProduto poderão ser adicionados nas views e acrescentados à controller, sem muita dificuldade seguindo o mesmo padrão empregado até aqui.

5. Conclusão

Para finalizar o nosso exemplo iremos acrescentar as ações Valor e Confirmar na Controller ProdutoController():


public ActionResult valor(string btnVoltar, string btnAvancar)
        {
            if (btnVoltar != null)
                return RedirectToAction("Fornecedor");
            else if (btnAvancar != null)
                return RedirectToAction("Confirmar");
            else
                return View(regProduto);
        }
 
        public ActionResult Confirmar(string btnVoltar, string btnConfirmar)
        {
            if (btnVoltar != null)
                return RedirectToAction("Valor");
            else if (btnConfirmar != null)
            {
                //Implementar persistência dos dados aqui
                return Content(“Dados confirmados!”);
            }
            else
                return View(regProduto);
        }

Após criar os métodos na controller vamos criar a view Valor na pasta /View/Produto/ com o seguinte conteúdo:


<h2>Produtos: Valor</h2>
        Preencha os dados abaixo
      <% using (Html.BeginForm())
      { %>
        <%: Html.ValidationSummary() %>
        <%: Html.Serialize("regProduto", Model) %>
        <p>Valor de Custo:<%: Html.EditorFor(x => x.Custo) %></p>
        <p>Preço à prazo:<%: Html.EditorFor(x => x.valorPrazo) %></p>
        <p>Preço à vista:<%: Html.EditorFor(x => x.valorVista) %></p>
        <p>
            <input type="submit" name="btnVoltar" value="< Voltar" />
            <input type="submit" name="btnAvancar" value="Avançar >" />
         </p>
     <% } %>

A view Confirmar na pasta /View/Produto/com o seguinte conteúdo:


<h2>Confirmar</h2>
        Confira se os dados cadastrais estão corretos <br />
        <% using (Html.BeginForm())
           { %>
        <%: Html.Serialize("regProduto", Model) %>
        <div> Descrição: <b><%: Model.Descricao %></b></div>
        <div> Categoria: <b><%: Model.Categoria %></b></div>
        <div> Referência: <b><%: Model.Referencia %></b></div>
        <div> Nome Fornecedor: <b><%: Model.FornecedorNome %></b></div>
        <div> Telefone Fornecedor: <b><%: Model.FornecedorTelefone %></b></div>
        <div> Valor de Custo: <b><%: Model.Custo %></b></div>
        <div> Venda à vista: <b><%: Model.valorVista %></b></div>
        <div> Venda à prazo: <b><%: Model.valorPrazo %></b></div>
        <p>
            <input type="submit" name="btnVoltar" value="< Voltar" />
            <input type="submit" name="btnConfirmar" value="Confirmar" />
        </p>
        <% } %>

Finalmente temos um exemplo completo de navegação wizard com preservação do objeto entre todas as views. A persistência dos dados poderá ser feita na ação Confirmar. Espero que tenha ajudado. Obrigado e até a próxima.