WebMatrix - Criando uma Loja Virtual - Definindo o processo de compra e incluindo e removendo itens no carrinho - 5


Na  quarta parte deste artigo incluímos a busca no catálogo de produtos e definimos o tratamento de erro básico da nossa loja virtual vamos agora definir como o usuário poderá realizar compras em nosso site.

Definindo o processo de compra

Vamos iniciar agora a etapa onde vamos definir o processo de compra que envolverá a criação de um carrinho de compras, um sistema de autenticação de usuários e a realização do check-out. Para isso vamos tratar de assuntos que envolverão os seguintes conceitos :

Nossa primeira tarefa será construir o carrinho de compras, que permitirá aos usuários armazenar uma lista de produtos e criar um pedido para checkout.

O carrinho de compras e o checkout vão funcionar de maneira tradicional.

Abaixo temos uma figura que detalhe esse processo de compras em nossa loja virtual:

Criando o carrinho de compras

Em nossa página de detalhes do produto, nós adicionamos um formulário de pedido para que os que posta os seus valores para a página a Carrinho.cshtml. Vamos implementar esta página.

Precisamos armazenar o conteúdo do carrinho de compras para a duração da visita do usuário ao site. A sessão do navegador é um lugar ideal para armazenar essa informação pois ela é mantida pelo servidor durante a visita do usuário e pode armazenar qualquer tipo de objeto.

Vamos criar uma classe que representara carrinho e que vai encapsular os dados e métodos necessários para realizar operações com o carrinho de compras no site. Nossa classe carrinho vai precisar para armazenar uma lista de itens do carrinho (produtos, descrição e preços) e vai precisar de métodos que nos permitam realizar operações de adicionar e remover itens individuais, remover todos os itens, e obter o valor total de todos os itens do carrinho.

Para garantir que a classe carrinho está disponível para todas as páginas dentro do aplicativo, iremos criá-la dentro da pasta App_Code. Qualquer código criado na pasta App_Code de um site estará disponível a todas as páginas do site, mas nunca será servido diretamente pelo IIS.

Assim, dentro da pasta App_Code, vamos adicionar um arquivo de classe chamado Carrinho.cs clicando com o botão direito do mouse sobre a pasta e selecionando a opção New File;

A seguir marque o template Class(C#) e informe o nome Carrinho.cs e clique no botão OK;

Em seguida defina o seguinte código neste arquivo:

using System;
using System.Collections.Generic;
using System.Web;
using System.Linq;
/// <summary>
/// TecnoSite carrinho de compras
/// </summary>
public class Carrinho
{
    private List<CarrinhoItem> _Itens = new List<CarrinhoItem>();
    public List<CarrinhoItem> Itens { get { return _Items; } }
    public decimal ValorTotal { get { return _Itens.Sum(p => p.Preco); } }

    public void AdicionarItem(int produtoID, string descricao, decimal preco)
    {
        _Itens.Add( new CarrinhoItem { ProdutoID = produtoID, Descricao = descricao, Preco = preco } );
    }
    public void Limpar()
    {
        _Itens.Clear();
    }
    public void RemoverItem(int index)
    {
        _Itens.RemoveAt(index);
    }
}

Este código define a classe Carrinho que representa o nosso carrinho de compras nela temos uma propriedade pública somente-leitura chamada Itens que retornada pelo campo privado _Itens, ambas são declaradas como do tipo List<CarrinhoItem>.

A classe CarrinhoItem será definida mais adiante e será usada para armazenar o código do produto, a descrição e o preço de um item individual do carrinho de compras.

Temos também uma propriedade pública chamada ValorTotal que o método List<T>.Sum() para obter o preço total dos itens no carrinho(CarrinhoItem) de compras usando uma expressão lambda.

A seguir temos 3 métodos :

Definindo a classe CarrinhoItem

Vamos definir a classe CarrinhoItem para armazenar cada item individual do carrinho de compras. Esta classe deve definir 3 propriedades públicas: ProdutoID, Descricao e Preco;

Poderíamos criar um novo arquivo chamado CarrinhoItem.cs e definir nele a classe mas podemos também criar a classe no mesmo arquivo onde definimos a classe Carrinho.

Abra então o arquivo Carrinho.cs e inclua no seu final o código abaixo:

/// <summary>
/// TecnoSite  item do carrinho
/// </summary>
public class CarrinhoItem
{
   public int ProdutoID { get; set; }
   public string Descricao { get; set; }
   public decimal Preco { get; set; }
}

Com isso concluímos a implementação das funcionalidades do nosso carrinho de compras e agora poderemos armazenar um objeto Carrinho em uma variável Session do navegador que representará um carrinho de compras para o usuário que estiver visitando o site e que será mantido enquanto durar a visita do cliente.

Definindo a página do carrinho de compras

Vamos criar uma página para armazenar e retornar os valores do objeto Carrinho e apresentar uma visão do carrinho de compras ao usuário. Esta página irá permitir também que os usuários removam itens do carrinho e realizem o checkout.

Esta página será baseada na nossa página de leiaute padrão definida em Shared/Layouts/_Layout.cshtml;

A primeira coisa que temos que fazer é retornar o objeto Carrinho atual da sessão do navegador do usuário. Se ainda não existir um carrinho não sessão devemos criar um carrinho vazio e incluí-lo na sessão.

Vamos fazer isso definindo o código abaixo no arquivo Carrinho.cshtml. Remova o código criado no arquivo e inclua o código abaixo :

@{
Layout = "~/Shared/Layouts/_Layout.cshtml";
Page.Title = "Carrinho";
// Cria um carrinho vazio na sessão se ele não exitir
if (Session["carrinho"] == null)
{
   Session["carrinho"] = new Carrinho();
}
// Pega o carrinho atual da Sessão
Carrinho carrinho = (Carrinho)Session["carrinho"];
}

Agora que temos um objeto Carrinho vamos incluir o código que permitirá iterar sobre a coleção Carrinho.Itens e gerando uma saída usando um tabela HTML.

<table id="TabelaCarrinho">
<tr>
  <th class="produto">Produto</th>
  <th class="descricao">Descricao</th>
  <th class="preco">Preço</th>
</tr>
@foreach (var item in carrinho.Itens)
{
 <tr>
   <td class="produto">@item.ProdutoID</td>
   <td class="descricao">@item.Descricao</td>
   <td class="preco">R$@item.Preco</td>
 </tr>
}
<tr class="carrinhoTotal">
  <td colspan="2">&nbsp;</td>
  <td>Total: R$@carrinho.ValorTotal</td>
</tr>
</table>

Na linha final da <table>, usamos a propriedade ValorTotal do objeto carrinho para exibir o preço total ao usuário. Antes de executar o projeto e dar uma olhada como ficou a página, vamos alterar o arquivo de estilo CSS na pasta CSS/style.css incluindo o código abaixo no final do arquivo :

/* Carrinho
---------------------------------*/
#TabelaCarrinho {
width: 100%;
border: none;
border-spacing:0px;
}
#TabelaCarrinho th {
border-bottom: 1px solid #cdcdcd;
}
#TabelaCarrinho td, th {
padding:5px;
}
#TabelaCarrinho .produto {
text-align:left;
width: 10%;
}
#TabelaCarrinho .descricao {
text-align:left;
width: 50%;
}
#TabelaCarrinho .preco {
text-align:right;
width: 20%;
}
#TabelaCarrinho .carrinhoTotal td {
margin-top: 10px;
border-top: 1px solid #cdcdcd;
text-align:right;
font-weight:bold;
}

Agora podemos executar o projeto, escolher um produto no catálogo e incluir o produto no carrinho.

Ao fazermos isso iremos obter o seguinte resultado:

Não exatamente isso que queríamos obter não é mesmo ?

Temos dois problemas :

1- O produto não foi incluído no carrinho
2- A página não informa ao cliente que o seu carrinho esta vazio.

Então vamos corrigir os problemas...

Se carrinho de compras do usuário estiver vazio, temos que emitir um aviso ao invès de exibir uma apenas uma tela vazia. Para fazer isso, vamos adicionar uma instrução if no topo do corpo da página Carrinho.cshtml que vai chamar o método Count() da coleção de itens do carrinho.

Primeiro vamos verificar para ver se a coleção tem todas as linhas. Se o carrinho não tem itens, vamos exibir uma mensagem adequada, caso contrário, vamos exibir os itens do carrinho.

Vamos então alterar o código do arquivo Carrinho.cshtml conforme abaixo:

@{
Layout = "~/Shared/Layouts/_Layout.cshtml";
Page.Title = "Carrinho";
// Cria um carrinho vazio na sessão se ele não exitir
if (Session["carrinho"] == null)
{
   Session["carrinho"] = new Carrinho();
}
// Pega o carrinho atual da Sessão
Carrinho carrinho = (Carrinho)Session["carrinho"];
}

<h2>Carrinho de Compras</h2>

@if(carrinho.Itens.Count() == 0)
{
    <p>Não existe nenhum item no seu carrinho de compras.</p>
}
else
{
  <text>  
    <table id="TabelaCarrinho">
    <tr>
    <th class="produto">Produto</th>
    <th class="descricao">Descricao</th>
    <th class="preco">Preço</th>
    </tr>
    @foreach (var item in carrinho.Itens)
    {
      <tr>
      <td class="produto">@item.ProdutoID</td>
      <td class="descricao">@item.Descricao</td>
      <td class="preco">R$@item.Preco</td>
      </tr>
    }
    <tr class="carrinhoTotal">
    <td colspan="2">&nbsp;</td>
    <td>Total: R$@carrinho.ValorTotal</td>
    </tr>
    </table>
  </text>
}

Executando novamente o projeto agora iremos obter:

Agora vamos ter que incluir os itens no carrinho para resolver o outro problema.

A página dos detalhes do produto(ProdutoDetalhes.cshtml) posta os valores do formulário de pedido para a página Carrinho.cshtml em três campos: ProdutoID, descrição e preço.

Para adicionar o produto escolhido no carrinho, vamos adicionar uma solicitação post ao bloco de código no topo da página do carrinho.

O código do manipulador da solicitação post simplesmente recupera os valores dos campos do formulário destacados e os passa para o método Carrinho.AdicionarItem(). Uma vez que o item foi adicionado ao carro, vamos salvá-lo e voltar para a sessão do usuário.

Adicione o manipulador de solicitações post, conforme o código destacado em azul abaixo na topo da página Carrinho.cshtml:


@{
Layout = "~/Shared/Layouts/_Layout.cshtml";
Page.Title = "Carrinho";
// Cria um carrinho vazio na sessão se ele não exitir
if (Session["carrinho"] == null)
{
   Session["carrinho"] = new Carrinho();
}
// Pega o carrinho atual da Sessão
Carrinho carrinho = (Carrinho)Session["carrinho"];

if (IsPost)
{
  var produtoID = Request["produtoID"];
  var descricao = Request["descricao"];
  var preco = Request["preco"];
  carrinho.AdicionarItem(produtoID.AsInt(), descricao, preco.AsDecimal());
}

}

<h2>Carrinho de Compras</h2>
...
...

Após estas alterações se rodarmos novamente o projeto e selecionarmos alguns produtos incluindo-os no carrinho iremos obter o seguinte resultado:

Vamos agora melhorar a apresentação e ao invés de exibir o código do produto vamos exibir o seu título.

Como podemos exibir o título do produto ao invés do seu código para o usuário ?

A classe CarrinhoItem não armazena o título do produto e para suprir esta lacuna teremos que implementar uma função helper para obter o título do produto do banco de dados.

@* Função Titulo Produto *@
@functions {
public static string GetNomeProdutoPorID(int produtoID)
{
  var db = Database.Open("TecnoSite");
  var sqlQuery = "SELECT titulo FROM Produtos WHERE produtoID = @0";
  return db.QuerySingle(sqlQuery, produtoID).titulo;
}
}

Definimos assim uma função chamada GetNomeProdutoPorID() que aceita o ID do produto como parâmetro, executa uma consulta no banco de dados e retorna o título do produto.

Agora, para exibir o título do produto vamos chamar esta função em um laço foreach no arquivo Carrinho.cshtml conforme o código abaixo:

{
Layout = "~/Shared/Layouts/_Layout.cshtml";
Page.Title = "Carrinho";
// Cria um carrinho vazio na sessão se ele não exitir
if (Session["carrinho"] == null)
{
   Session["carrinho"] = new Carrinho();
}
// Pega o carrinho atual da Sessão
Carrinho carrinho = (Carrinho)Session["carrinho"];

if (IsPost)
{
  var _produtoID = Request["produtoID"];
  var _descricao = Request["descricao"];
  var _preco = Request["preco"];
  carrinho.AdicionarItem(_produtoID.AsInt(), _descricao, _preco.AsDecimal());
}

}

<h2>Carrinho de Compras</h2>

@if(carrinho.Itens.Count() == 0)
{
    <p>Não existe nenhum item no seu carrinho de compras.</p>
}
else
{
  <text>  
    <table id="TabelaCarrinho">
    <tr>
    <th class="produto">Produto</th>
    <th class="descricao">Descricao</th>
    <th class="preco">Preço</th>
    </tr>
    @foreach (var item in carrinho.Itens)
    {
      <tr>
      <td class="produto">@Produtos.GetNomeProdutoPorID(item.ProdutoID)</td>
      <td class="descricao">@item.Descricao</td>
      <td class="preco">R$@item.Preco</td>
      </tr>
    }
    <tr class="carrinhoTotal">
    <td colspan="2">&nbsp;</td>
    <td>Total: R$@carrinho.ValorTotal</td>
    </tr>
    </table>
  </text>
}

Executando o projeto novamente agora iremos obter o seguinte:

Removendo itens do carrinho de compras

Se o usuário adicionar um item incorreto ao seu carrinho de compras ou mudar as suas intenção sobre um pedido, ele precisa ser capaz de remover o item do carrinho.

Para conseguir isso, vamos incluir um botão "Remover" botão para cada item no carrinho de compras e quando o usuário clicar no botão, o item será removido diretamente do carrinho.

Nenhuma página de confirmação será necessária, pois é uma tarefa trivial adicionar novamente o item removido.

A especificação W3C HTML determina que não se deve fazer alterações em dados persistidos, como parte de uma solicitação HTTP GET. Embora o nosso carrinho não esta sendo armazenado em um banco de dados, seus dados são mantidos na sessão do navegador, por isso devemos usar uma solicitação POST.

Como você ja deve saber, podemos ter para um única página múltiplos formulários HTML, desde que eles não estejam aninhados ou sobrepostos. Este pode ser um conceito estranho para os desenvolvedores Web Forms, onde se trabalha com um formulário para cada página, mas em casos como este, é extremamente útil usar este recurso.

No nosso exemplo vamos renderizar um formulário separado para cada linha da tabela de conteúdo do carrinho, o qual irá dar um POST dos dados para a página Carrinho.cshtml.

O formulário é composto por um campo oculto que armazena o índice da linha e um botão submit. Quando o formulário é submetido, nós vamos passar o conteúdo do campo oculto para o o método Carrinho.RemoveItem().

Este método requer o índice do item como parâmetro. O formulário, incluindo o botão, será exibido em uma coluna adicional que iremos incluir no lado direito da tabela existente.

Atualmente, o carrinho Carrinho.cshtml usa um laço foreach para iterar sobre os itens do carrinho e construir o conteúdo da tabela. Precisamos passar o índice de linha de base zero para o método Carrinho.RemoverItem(), e precisaremos declarar uma variável para armazenar o índice antes de entrarmos no loop e incrementá-lo em cada iteração.

Vamos então incluir o código(destacado em azul) com todas essas funcionalidades no arquivo Carrinho.cshtml conforme abaixo:

@{
Layout = "~/Shared/Layouts/_Layout.cshtml";
Page.Title = "Carrinho";
// Cria um carrinho vazio na sessão se ele não exitir
if (Session["carrinho"] == null)
{
   Session["carrinho"] = new Carrinho();
}
// Pega o carrinho atual da Sessão
Carrinho carrinho = (Carrinho)Session["carrinho"];

if (IsPost)
{
  var _produtoID = Request["produtoID"];
  var _descricao = Request["descricao"];
  var _preco = Request["preco"];
  carrinho.AdicionarItem(_produtoID.AsInt(), _descricao, _preco.AsDecimal());
}

}

<h2>Carrinho de Compras</h2>

@if(carrinho.Itens.Count() == 0)
{
    <p>Não existe nenhum item no seu carrinho de compras.</p>
}
else
{
  <text>  
    <table id="TabelaCarrinho">
    <tr>
      <th class="produto">Produto</th>
      <th class="descricao">Descricao</th>
      <th class="preco">Preço</th>
      <th>&nbsp;</th>
    </tr>
        
   @{
      // Declara e incializar a variável index i
      int i = 0;
    }
        
    @foreach (var item in carrinho.Itens)
    {
      <tr>
         <td class="produto">@Produtos.GetNomeProdutoPorID(item.ProdutoID)</td>
         <td class="descricao">@item.Descricao</td>
         <td class="preco">R$@item.Preco</td>
        
         <td class="remover">
            <form action="Carrinho" method="post">
               @Html.Hidden("removerIndice", i)
               <input type="submit" value="Remover" />
            </form>
         </td>
      </tr>
        
      //incrementa o indice
      i++;
    }
     <tr class="carrinhoTotal">
       <td colspan="2">&nbsp;</td>
       <td>Total: R$@carrinho.ValorTotal</td>
       <td>&nbsp;</td>
     </tr>
    </table>
  </text>
}

Executando o projeto novamente teremos:

Agora temos que realizar um pequeno ajuste na página carrinho.cshtml pois o formulário de dados postado a partir da página ProdutosDetalhes.cshtml via botão - Incluir no Carrinho - e a partir da página Carrinho.cshtml via botão - Remover.

Se a página passar o código do produto (ProdutoId) no formulário de dados a requisição POST esta vindo da página ProdutosDetalhes.cshtml via botão - Incluir no Carrinho - e temos que chamar o método AdicionarItem().

Se a requisição POST contém um valor para removerIndice sabemos que ela vem do acionamento do botão Remover do formulário Carrinho.cshtml. A variável removerIndice contém um índice da linha armazenado no campo oculto(hidden) e podemos passar este valor para o método Carrinho.RemoverIndice() para deletar a linha da coleção de itens do carrinho.

Vamos então aplicar o código destacado em azul na página carrinho.cshtml:

{
Layout = "~/Shared/Layouts/_Layout.cshtml";
Page.Title = "Carrinho";
// Cria um carrinho vazio na sessão se ele não exitir
if (Session["carrinho"] == null)
{
   Session["carrinho"] = new Carrinho();
}
// Pega o carrinho atual da Sessão
Carrinho carrinho = (Carrinho)Session["carrinho"];

if (IsPost)
{
    if (Request["produtoID"] != null)
    {
       //formulário postado pela página Detalhes do produto 
       var _produtoID = Request["produtoID"];
       var _descricao = Request["descricao"];
       var _preco = Request["preco"];
       carrinho.AdicionarItem(_produtoID.AsInt(), _descricao, _preco.AsDecimal());
    }
    else if (Request["removerIndice"] != null)
    {
        // Formulario postado pelo botão Remover Item do Carrinho
        var removerIndice = Request["removerIndice"].AsInt();
        carrinho.RemoverItem(removerIndice);
    }
}

}

<h2>Carrinho de Compras</h2>

@if(carrinho.Itens.Count() == 0)
{
    <p>Não existe nenhum item no seu carrinho de compras.</p>
}
else
{
  <text>  
    <table id="TabelaCarrinho">
    <tr>
      <th class="produto">Produto</th>
      <th class="descricao">Descricao</th>
      <th class="preco">Preço</th>
      <th>&nbsp;</th>
    </tr>
        
   @{
      // Declara e incializar a variável index i
      int i = 0;
    }
        
    @foreach (var item in carrinho.Itens)
    {
      <tr>
         <td class="produto">@Produtos.GetNomeProdutoPorID(item.ProdutoID)</td>
         <td class="descricao">@item.Descricao</td>
         <td class="preco">R$@item.Preco</td>
        
         <td class="remover">
            <form action="Carrinho" method="post">
               @Html.Hidden("removerIndice", i)
               <input type="submit" value="Remover" />
            </form>
         </td>
      </tr>
        
      //incrementa o indice
      i++;
    }
     <tr class="carrinhoTotal">
       <td colspan="2">&nbsp;</td>
       <td>Total: R$@carrinho.ValorTotal</td>
       <td>&nbsp;</td>
     </tr>
    </table>
  </text>
}

Vamos incluir também no arquivo de estilos Style.css o código abaixo para aplicar um estilo na coluna remover da tabela:

...
table .remover {
text-align:right;
width: 20%;
}

Agora com essas implementações o cliente poderá remover itens do seu carrinho de compras.

Aguarde a quinta parte do artigo: WebMatrix - Criando uma Loja Virtual - Continuando as compras e incluindo a autenticação do cliente - 6

Referências:


José Carlos Macoratti