Partilhar via


Parte 5: Lógica de negócios

por Joe Stagner

A Tailspin Spyworks demonstra como é extraordinariamente simples criar aplicativos avançados e escalonáveis para a plataforma .NET. Ele mostra como usar os ótimos novos recursos no ASP.NET 4 para criar uma loja online, incluindo compras, check-out e administração.

Esta série de tutoriais detalha todas as etapas executadas para criar o aplicativo de exemplo Tailspin Spyworks. A parte 5 adiciona alguma lógica de negócios.

Adicionando alguma lógica de negócios

Queremos que nossa experiência de compra esteja disponível sempre que alguém visitar nosso site. Os visitantes poderão navegar e adicionar itens ao carrinho de compras, mesmo que não estejam registrados ou conectados. Quando estiverem prontos para marcar, eles terão a opção de autenticar e, se ainda não forem membros, poderão criar uma conta.

Isso significa que precisaremos implementar a lógica para converter o carrinho de compras de um estado anônimo em um estado "Usuário Registrado".

Vamos criar um diretório chamado "Classes" e, em seguida, Right-Click na pasta e criar um novo arquivo "Classe" chamado MyShoppingCart.cs

Captura de tela que mostra o novo arquivo de classe chamado Meu Carrinho de Compras ponto C S.

Captura de tela que mostra o conteúdo da pasta Classes.

Como mencionado anteriormente, estenderemos a classe que implementa a página MyShoppingCart.aspx e faremos isso usando . O poderoso constructo "Classe Parcial" do NET.

A chamada gerada para nosso arquivo MyShoppingCart.aspx.cf tem esta aparência.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace TailspinSpyworks
{
    public partial class MyShoppingCart : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {

        }
    }
}

Observe o uso do palavra-chave "parcial".

O arquivo de classe que acabamos de gerar tem esta aparência.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace TailspinSpyworks.Classes
{
    public class MyShoppingCart
    {
    }
}

Vamos mesclar nossas implementações adicionando a palavra-chave parcial a esse arquivo também.

Nosso novo arquivo de classe agora tem esta aparência.

namespace TailspinSpyworks.Classes
{
    public partial class MyShoppingCart
    {
    }
}

O primeiro método que adicionaremos à nossa classe é o método "AddItem". Esse é o método que, em última análise, será chamado quando o usuário clicar nos links "Adicionar à Arte" nas páginas Lista de Produtos e Detalhes do Produto.

Acrescente o seguinte às instruções using na parte superior da página.

using TailspinSpyworks.Data_Access;

E adicione esse método à classe MyShoppingCart.

//------------------------------------------------------------------------------------+
public void AddItem(string cartID, int productID, int quantity)
{
  using (CommerceEntities db = new CommerceEntities())
    {
    try 
      {
      var myItem = (from c in db.ShoppingCarts where c.CartID == cartID && 
                              c.ProductID == productID select c).FirstOrDefault();
      if(myItem == null)
        {
        ShoppingCart cartadd = new ShoppingCart();
        cartadd.CartID = cartID;
        cartadd.Quantity = quantity;
        cartadd.ProductID = productID;
        cartadd.DateCreated = DateTime.Now;
        db.ShoppingCarts.AddObject(cartadd);
        }
      else
        {
        myItem.Quantity += quantity;
        }
      db.SaveChanges();
      }
    catch (Exception exp)
      {
      throw new Exception("ERROR: Unable to Add Item to Cart - " + 
                                                          exp.Message.ToString(), exp);
      }
   }
}

Estamos usando LINQ to Entities para ver se o item já está no carrinho. Nesse caso, atualizamos a quantidade de pedidos do item; caso contrário, criamos uma nova entrada para o item selecionado

Para chamar esse método, implementaremos uma página AddToCart.aspx que não apenas classicionar esse método, mas, em seguida, exibirá as compras atuais a=carrinho depois que o item tiver sido adicionado.

Right-Click no nome da solução no gerenciador de soluções e adicione uma nova página chamada AddToCart.aspx como fizemos anteriormente.

Embora possamos usar essa página para exibir resultados provisórios, como problemas de estoque baixos, etc., em nossa implementação, a página não será renderizada, mas chamará a lógica "Adicionar" e redirecionará.

Para fazer isso, adicionaremos o código a seguir ao evento Page_Load.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Diagnostics;

namespace TailspinSpyworks
{
    public partial class AddToCart : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            string rawId = Request.QueryString["ProductID"];
            int productId;
            if (!String.IsNullOrEmpty(rawId) && Int32.TryParse(rawId, out productId))
            {
                MyShoppingCart usersShoppingCart = new MyShoppingCart();
                String cartId = usersShoppingCart.GetShoppingCartId();
                usersShoppingCart.AddItem(cartId, productId, 1);
            }
            else
            {
                Debug.Fail("ERROR : We should never get to AddToCart.aspx 
                                                   without a ProductId.");
                throw new Exception("ERROR : It is illegal to load AddToCart.aspx 
                                                   without setting a ProductId.");
            }
            Response.Redirect("MyShoppingCart.aspx");
        }
    }
}

Observe que estamos recuperando o produto para adicionar ao carrinho de compras de um parâmetro QueryString e chamando o método AddItem de nossa classe.

Supondo que nenhum erro seja encontrado, o controle será passado para a página SHoppingCart.aspx que implementaremos totalmente em seguida. Se houver um erro, lançaremos uma exceção.

Atualmente, ainda não implementamos um manipulador de erros global, portanto, essa exceção não seria tratada pelo nosso aplicativo, mas corrigiremos isso em breve.

Observe também o uso da instrução Debug.Fail() (disponível via using System.Diagnostics;)

Se o aplicativo estiver em execução dentro do depurador, esse método exibirá uma caixa de diálogo detalhada com informações sobre o estado dos aplicativos, juntamente com a mensagem de erro que especificamos.

Ao executar em produção, a instrução Debug.Fail() é ignorada.

Você observará no código acima uma chamada para um método em nossos nomes de classe de carrinho de compras "GetShoppingCartId".

Adicione o código para implementar o método da seguinte maneira.

Observe que também adicionamos botões de atualização e check-out e um rótulo em que podemos exibir o "total" do carrinho.

public const string CartId = "TailSpinSpyWorks_CartID";

//--------------------------------------------------------------------------------------+
public String GetShoppingCartId()
{
  if (Session[CartId] == null)
     {
     Session[CartId] = System.Web.HttpContext.Current.Request.IsAuthenticated ? 
                                        User.Identity.Name : Guid.NewGuid().ToString();
     }
  return Session[CartId].ToString();
}

Agora podemos adicionar itens ao carrinho de compras, mas não implementamos a lógica para exibir o carrinho depois que um produto tiver sido adicionado.

Portanto, na página MyShoppingCart.aspx, adicionaremos um controle EntityDataSource e um controle GridVire da seguinte maneira.

<div id="ShoppingCartTitle" runat="server" class="ContentHead">Shopping Cart</div>
<asp:GridView ID="MyList" runat="server" AutoGenerateColumns="False" ShowFooter="True" 
                          GridLines="Vertical" CellPadding="4"
                          DataSourceID="EDS_Cart"  
                          DataKeyNames="ProductID,UnitCost,Quantity" 
                          CssClass="CartListItem">              
  <AlternatingRowStyle CssClass="CartListItemAlt" />
  <Columns>
    <asp:BoundField DataField="ProductID" HeaderText="Product ID" ReadOnly="True" 
                                          SortExpression="ProductID"  />
    <asp:BoundField DataField="ModelNumber" HeaderText="Model Number" 
                                            SortExpression="ModelNumber" />
    <asp:BoundField DataField="ModelName" HeaderText="Model Name" 
                                          SortExpression="ModelName"  />
    <asp:BoundField DataField="UnitCost" HeaderText="Unit Cost" ReadOnly="True" 
                                         SortExpression="UnitCost" 
                                         DataFormatString="{0:c}" />         
    <asp:TemplateField> 
      <HeaderTemplate>Quantity</HeaderTemplate>
      <ItemTemplate>
         <asp:TextBox ID="PurchaseQuantity" Width="40" runat="server" 
                      Text='<%# Bind("Quantity") %>'></asp:TextBox> 
      </ItemTemplate>
    </asp:TemplateField>           
    <asp:TemplateField> 
      <HeaderTemplate>Item Total</HeaderTemplate>
      <ItemTemplate>
        <%# (Convert.ToDouble(Eval("Quantity")) *  
             Convert.ToDouble(Eval("UnitCost")))%>
      </ItemTemplate>
    </asp:TemplateField>
    <asp:TemplateField> 
    <HeaderTemplate>Remove Item</HeaderTemplate>
      <ItemTemplate>
        <center>
          <asp:CheckBox id="Remove" runat="server" />
        </center>
      </ItemTemplate>
    </asp:TemplateField>
  </Columns>
  <FooterStyle CssClass="CartListFooter"/>
  <HeaderStyle  CssClass="CartListHead" />
</asp:GridView>

<div>
  <strong>
    <asp:Label ID="LabelTotalText" runat="server" Text="Order Total : ">  
    </asp:Label>
    <asp:Label CssClass="NormalBold" id="lblTotal" runat="server" 
                                                   EnableViewState="false">
    </asp:Label>
  </strong> 
</div>
<br />
<asp:imagebutton id="UpdateBtn" runat="server" ImageURL="Styles/Images/update_cart.gif" 
                                onclick="UpdateBtn_Click"></asp:imagebutton>
<asp:imagebutton id="CheckoutBtn" runat="server"  
                                  ImageURL="Styles/Images/final_checkout.gif"    
                                  PostBackUrl="~/CheckOut.aspx">
</asp:imagebutton>
<asp:EntityDataSource ID="EDS_Cart" runat="server" 
                      ConnectionString="name=CommerceEntities" 
                      DefaultContainerName="CommerceEntities" EnableFlattening="False" 
                      EnableUpdate="True" EntitySetName="ViewCarts" 
                      AutoGenerateWhereClause="True" EntityTypeFilter="" Select=""                         
                      Where="">
  <WhereParameters>
    <asp:SessionParameter Name="CartID" DefaultValue="0" 
                                        SessionField="TailSpinSpyWorks_CartID" />
  </WhereParameters>
</asp:EntityDataSource>

Chame o formulário no designer para que você possa clicar duas vezes no botão Atualizar Carrinho e gerar o manipulador de eventos de clique especificado na declaração na marcação.

Implementaremos os detalhes mais tarde, mas isso nos permitirá criar e executar nosso aplicativo sem erros.

Ao executar o aplicativo e adicionar um item ao carrinho de compras, você verá isso.

Captura de tela que mostra o carrinho de compras atualizado.

Observe que desviamos da exibição de grade "padrão" implementando três colunas personalizadas.

O primeiro é um campo Editável, "Associado" para a Quantidade:

<asp:TemplateField> 
      <HeaderTemplate>Quantity</HeaderTemplate>
      <ItemTemplate>
         <asp:TextBox ID="PurchaseQuantity" Width="40" runat="server" 
                      Text='<%# Bind("Quantity") %>'></asp:TextBox> 
      </ItemTemplate>
    </asp:TemplateField>

A próxima é uma coluna "calculada" que exibe o total do item de linha (o custo do item vezes a quantidade a ser ordenada):

<asp:TemplateField> 
      <HeaderTemplate>Item Total</HeaderTemplate>
      <ItemTemplate>
        <%# (Convert.ToDouble(Eval("Quantity")) *  
             Convert.ToDouble(Eval("UnitCost")))%>
      </ItemTemplate>
    </asp:TemplateField>

Por fim, temos uma coluna personalizada que contém um controle CheckBox que o usuário usará para indicar que o item deve ser removido do gráfico de compras.

<asp:TemplateField> 
    <HeaderTemplate>Remove Item</HeaderTemplate>
      <ItemTemplate>
        <center>
          <asp:CheckBox id="Remove" runat="server" />
        </center>
      </ItemTemplate>
    </asp:TemplateField>

Captura de tela que mostra a Quantidade atualizada e Remover Itens.

Como você pode ver, a linha Total da Ordem está vazia, portanto, vamos adicionar alguma lógica para calcular o Total da Ordem.

Primeiro, implementaremos um método "GetTotal" em nossa Classe MyShoppingCart.

No arquivo MyShoppingCart.cs, adicione o código a seguir.

//--------------------------------------------------------------------------------------+
public decimal GetTotal(string cartID)
{
    using (CommerceEntities db = new CommerceEntities())
    {
        decimal cartTotal = 0;
        try
        {
            var myCart = (from c in db.ViewCarts where c.CartID == cartID select c);
            if (myCart.Count() > 0)
            {
                cartTotal = myCart.Sum(od => (decimal)od.Quantity * (decimal)od.UnitCost);
            }
        }
        catch (Exception exp)
        {
            throw new Exception("ERROR: Unable to Calculate Order Total - " + 
            exp.Message.ToString(), exp);
        }
        return (cartTotal);
     }
}

Em seguida, no manipulador de eventos Page_Load, poderemos chamar nosso método GetTotal. Ao mesmo tempo, adicionaremos um teste para ver se o carrinho de compras está vazio e ajustaremos a tela adequadamente se estiver.

Agora, se o carrinho de compras estiver vazio, teremos o seguinte:

Captura de tela que mostra o carrinho de compras vazio.

E se não, vemos nosso total.

Captura de tela que mostra o valor total dos itens no carrinho de compras.

No entanto, esta página ainda não foi concluída.

Precisaremos de lógica adicional para recalcular o carrinho de compras removendo itens marcados para remoção e determinando novos valores de quantidade, pois alguns podem ter sido alterados na grade pelo usuário.

Vamos adicionar um método "RemoveItem" à nossa classe de carrinho de compras em MyShoppingCart.cs para lidar com o caso quando um usuário marca um item para remoção.

//------------------------------------------------------------------------------------+
public void RemoveItem(string cartID, int  productID)
{
    using (CommerceEntities db = new CommerceEntities())
    {
        try
        {
            var myItem = (from c in db.ShoppingCarts where c.CartID == cartID && 
                         c.ProductID == productID select c).FirstOrDefault();
            if (myItem != null)
            {
                db.DeleteObject(myItem);
                db.SaveChanges();
            }
        }
        catch (Exception exp)
        {
            throw new Exception("ERROR: Unable to Remove Cart Item - " + 
                                  exp.Message.ToString(), exp);
        }
    }
}

Agora, vamos criar um método para lidar com a circunstância quando um usuário simplesmente altera a qualidade a ser ordenada no GridView.

//--------------------------------------------------------------------------------------+
public void UpdateItem(string cartID, int productID, int quantity)
{
    using (CommerceEntities db = new CommerceEntities())
    {
        try
        {
            var myItem = (from c in db.ShoppingCarts where c.CartID == cartID && 
                    c.ProductID == productID select c).FirstOrDefault();
            if (myItem != null)
            {
                myItem.Quantity = quantity;
                db.SaveChanges();
            }
        }
        catch (Exception exp)
        {
            throw new Exception("ERROR: Unable to Update Cart Item - " +     
                                exp.Message.ToString(), exp);
        }
    }
}

Com os recursos básicos Remover e Atualizar em vigor, podemos implementar a lógica que realmente atualiza o carrinho de compras no banco de dados. (Em MyShoppingCart.cs)

//-------------------------------------------------------------------------------------+
public void UpdateShoppingCartDatabase(String cartId, 
                                       ShoppingCartUpdates[] CartItemUpdates)
{
  using (CommerceEntities db = new CommerceEntities())
    {
    try
      {
      int CartItemCOunt = CartItemUpdates.Count();
      var myCart = (from c in db.ViewCarts where c.CartID == cartId select c);
      foreach (var cartItem in myCart)
        {
        // Iterate through all rows within shopping cart list
        for (int i = 0; i < CartItemCOunt; i++)
          {
          if (cartItem.ProductID == CartItemUpdates[i].ProductId)
             {
             if (CartItemUpdates[i].PurchaseQantity < 1 || 
   CartItemUpdates[i].RemoveItem == true)
                {
                RemoveItem(cartId, cartItem.ProductID);
                }
             else 
                {
                UpdateItem(cartId, cartItem.ProductID, 
                                   CartItemUpdates[i].PurchaseQantity);
                }
              }
            }
          }
        }
      catch (Exception exp)
        {
        throw new Exception("ERROR: Unable to Update Cart Database - " + 
                             exp.Message.ToString(), exp);
        }            
    }           
}

Você observará que esse método espera dois parâmetros. Uma é a ID do carrinho de compras e a outra é uma matriz de objetos do tipo definido pelo usuário.

Para minimizar a dependência de nossa lógica em especificações de interface do usuário, definimos uma estrutura de dados que podemos usar para passar os itens do carrinho de compras para nosso código sem que nosso método precise acessar diretamente o controle GridView.

public struct ShoppingCartUpdates
{
    public int ProductId;
    public int PurchaseQantity;
    public bool RemoveItem;
}

Em nosso arquivo MyShoppingCart.aspx.cs, podemos usar essa estrutura em nosso manipulador de eventos Update Button Click da seguinte maneira. Observe que, além de atualizar o carrinho, atualizaremos o total do carrinho também.

//--------------------------------------------------------------------------------------+
protected void UpdateBtn_Click(object sender, ImageClickEventArgs e)
{
  MyShoppingCart usersShoppingCart = new MyShoppingCart();
  String cartId = usersShoppingCart.GetShoppingCartId();

  ShoppingCartUpdates[] cartUpdates = new ShoppingCartUpdates[MyList.Rows.Count];
  for (int i = 0; i < MyList.Rows.Count; i++)
    {
    IOrderedDictionary rowValues = new OrderedDictionary();
    rowValues = GetValues(MyList.Rows[i]);
    cartUpdates[i].ProductId =  Convert.ToInt32(rowValues["ProductID"]);
    cartUpdates[i].PurchaseQantity = Convert.ToInt32(rowValues["Quantity"]); 

    CheckBox cbRemove = new CheckBox();
    cbRemove = (CheckBox)MyList.Rows[i].FindControl("Remove");
    cartUpdates[i].RemoveItem = cbRemove.Checked;
    }

   usersShoppingCart.UpdateShoppingCartDatabase(cartId, cartUpdates);
   MyList.DataBind();
   lblTotal.Text = String.Format("{0:c}", usersShoppingCart.GetTotal(cartId));
}

Observe com interesse específico esta linha de código:

rowValues = GetValues(MyList.Rows[i]);

GetValues() é uma função auxiliar especial que implementaremos em MyShoppingCart.aspx.cs da seguinte maneira.

//--------------------------------------------------------------------------------------+
public static IOrderedDictionary GetValues(GridViewRow row)
{
  IOrderedDictionary values = new OrderedDictionary();
  foreach (DataControlFieldCell cell in row.Cells)
    {
    if (cell.Visible)
      {
      // Extract values from the cell
      cell.ContainingField.ExtractValuesFromCell(values, cell, row.RowState, true);
      }
    }
    return values;
}

Isso fornece uma maneira limpo de acessar os valores dos elementos associados em nosso controle GridView. Como nosso controle CheckBox "Remover Item" não está associado, vamos acessá-lo por meio do método FindControl().

Nesta fase do desenvolvimento do seu projeto, estamos nos preparando para implementar o processo de check-out.

Antes disso, vamos usar o Visual Studio para gerar o banco de dados de associação e adicionar um usuário ao repositório de associação.