Udostępnij za pośrednictwem


Część 5. Logika biznesowa

Autor: Joe Stagner

Tailspin Spyworks pokazuje, jak niezwykle proste jest tworzenie zaawansowanych, skalowalnych aplikacji dla platformy .NET. Pokazuje, jak używać wspaniałych nowych funkcji w ASP.NET 4 do tworzenia sklepu internetowego, w tym zakupów, wyewidencjonowania i administracji.

W tej serii samouczków szczegółowo opisano wszystkie kroki podjęte w celu utworzenia przykładowej aplikacji Tailspin Spyworks. Część 5 dodaje logikę biznesową.

Dodawanie logiki biznesowej

Chcemy, aby nasze doświadczenie handlowe było dostępne za każdym razem, gdy ktoś odwiedza naszą witrynę internetową. Odwiedzający będą mogli przeglądać i dodawać elementy do koszyka, nawet jeśli nie są zarejestrowani lub zalogowani. Gdy będą gotowe do wyewidencjonowania, otrzymają opcję uwierzytelnienia, a jeśli jeszcze nie są członkami, będą mogli utworzyć konto.

Oznacza to, że musimy zaimplementować logikę, aby przekonwertować koszyk z stanu anonimowego na stan "Zarejestrowany użytkownik".

Utwórzmy katalog o nazwie "Classes", a następnie Right-Click w folderze i utwórzmy nowy plik "Class" o nazwie MyShoppingCart.cs

Zrzut ekranu przedstawiający nowy plik Klasy o nazwie My Shopping Cart dot C S.

Zrzut ekranu przedstawiający zawartość folderu Classes.

Jak wspomniano wcześniej, rozszerzymy klasę, która implementuje stronę MyShoppingCart.aspx i zrobimy to za pomocą polecenia . Zaawansowana konstrukcja "Częściowa klasa" platformy NET.

Wygenerowane wywołanie naszego pliku MyShoppingCart.aspx.cf wygląda następująco.

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

        }
    }
}

Zwróć uwagę na użycie słowa kluczowego "partial".

Wygenerowany plik klasy wygląda następująco.

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

namespace TailspinSpyworks.Classes
{
    public class MyShoppingCart
    {
    }
}

Scalimy nasze implementacje, dodając również częściowe słowo kluczowe do tego pliku.

Nasz nowy plik klasy wygląda teraz następująco.

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

Pierwszą metodą dodaną do naszej klasy jest metoda "AddItem". Jest to metoda, która ostatecznie zostanie wywołana, gdy użytkownik kliknie linki "Dodaj do sztuki" na stronach Lista produktów i Szczegóły produktu.

Dołącz poniższe instrukcje do instrukcji using w górnej części strony.

using TailspinSpyworks.Data_Access;

Dodaj tę metodę do klasy 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);
      }
   }
}

Używamy LINQ to Entities, aby sprawdzić, czy element znajduje się już w koszyku. Jeśli tak, zaktualizujemy ilość zamówienia elementu, w przeciwnym razie utworzymy nowy wpis dla wybranego elementu

Aby wywołać tę metodę, zaimplementujemy stronę AddToCart.aspx, która nie tylko klasyfikuje tę metodę, ale następnie wyświetli bieżące zakupy a=cart po dodaniu elementu.

Right-Click nazwę rozwiązania w Eksploratorze rozwiązań i dodaj i nową stronę o nazwie AddToCart.aspx, jak to zrobiliśmy wcześniej.

Chociaż możemy użyć tej strony do wyświetlania wyników tymczasowych, takich jak problemy z niskimi zapasami itp., w naszej implementacji strona nie zostanie faktycznie renderowana, ale raczej wywoła logikę "Dodaj" i przekierowanie.

W tym celu dodamy następujący kod do zdarzenia 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");
        }
    }
}

Należy pamiętać, że pobieramy produkt, aby dodać go do koszyka z parametru QueryString i wywołujemy metodę AddItem naszej klasy.

Przy założeniu, że kontrolka nie zostanie przekazana do strony SHoppingCart.aspx, która zostanie w pełni zaimplementowana. Jeśli wystąpi błąd, zgłaszamy wyjątek.

Obecnie nie zaimplementowaliśmy jeszcze globalnej procedury obsługi błędów, więc ten wyjątek nie będzie obsługiwane przez naszą aplikację, ale wkrótce to naprawimy.

Zwróć również uwagę na użycie instrukcji Debug.Fail() (dostępnej za pośrednictwem polecenia Debug.Fail() using System.Diagnostics;)

Czy aplikacja jest uruchomiona wewnątrz debugera, ta metoda wyświetli szczegółowe okno dialogowe z informacjami o stanie aplikacji wraz z określonym komunikatem o błędzie.

Podczas uruchamiania w środowisku produkcyjnym instrukcja Debug.Fail() jest ignorowana.

W powyższym kodzie zostanie wyświetlone wywołanie metody w nazwach klas koszyka zakupów "GetShoppingCartId".

Dodaj kod, aby zaimplementować metodę w następujący sposób.

Pamiętaj, że dodaliśmy również przyciski aktualizacji i wyewidencjonowania oraz etykietę, w której można wyświetlić koszyk "total".

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

Teraz możemy dodać elementy do koszyka, ale nie zaimplementowaliśmy logiki wyświetlania koszyka po dodaniu produktu.

Dlatego na stronie MyShoppingCart.aspx dodamy kontrolkę EntityDataSource i kontrolkę GridVire w następujący sposób.

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

Wywołaj formularz w projektancie, aby można było kliknąć dwukrotnie przycisk Aktualizuj koszyk i wygenerować procedurę obsługi zdarzeń kliknięcia, która jest określona w deklaracji w znaczniku.

Szczegóły zostaną zaimplementowane później, ale pozwoli nam to skompilować i uruchomić naszą aplikację bez błędów.

Po uruchomieniu aplikacji i dodaniu elementu do koszyka zakupów zobaczysz ten element.

Zrzut ekranu przedstawiający zaktualizowany koszyk.

Pamiętaj, że odchyliliśmy się od ekranu siatki "domyślnej", implementując trzy kolumny niestandardowe.

Pierwszy to pole Edytowalne, "Powiązane" dla wartości Quantity:

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

Następną kolumną jest kolumna "calculated", która wyświetla sumę elementu wiersza (koszt elementu jest naliczany za kolejną liczbę):

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

Na koniec mamy kolumnę niestandardową zawierającą kontrolkę CheckBox, która będzie używana przez użytkownika, aby wskazać, że element powinien zostać usunięty z wykresu zakupowego.

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

Zrzut ekranu przedstawiający zaktualizowaną ilość i usuń elementy.

Jak widać, wiersz Order Total jest pusty, więc dodajmy logikę, aby obliczyć sumę zamówienia.

Najpierw zaimplementujemy metodę "GetTotal" do klasy MyShoppingCart.

W pliku MyShoppingCart.cs dodaj następujący kod.

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

Następnie w procedurze obsługi zdarzeń Page_Load możemy wywołać metodę GetTotal. W tym samym czasie dodamy test, aby sprawdzić, czy koszyk jest pusty i odpowiednio dostosujemy wyświetlacz, jeśli jest.

Teraz, jeśli koszyk jest pusty, otrzymamy następujące karty:

Zrzut ekranu przedstawiający pusty koszyk.

A jeśli nie, widzimy naszą sumę.

Zrzut ekranu przedstawiający łączną kwotę elementów w koszyku.

Jednak ta strona nie została jeszcze ukończona.

Będziemy potrzebować dodatkowej logiki, aby ponownie obliczyć koszyk, usuwając elementy oznaczone do usunięcia i określając nowe wartości ilości, ponieważ niektóre mogły zostać zmienione w siatce przez użytkownika.

Dodajmy metodę "RemoveItem" do klasy koszyka zakupów w pliku MyShoppingCart.cs, aby obsłużyć przypadek, gdy użytkownik oznaczy element do usunięcia.

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

Teraz reklamujmy metodę obsługi okoliczności, gdy użytkownik po prostu zmienia jakość, która ma zostać uporządkowana w siatce.

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

Dzięki podstawowym funkcjom Remove and Update możemy zaimplementować logikę, która faktycznie aktualizuje koszyk w bazie danych. (W pliku 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);
        }            
    }           
}

Należy pamiętać, że ta metoda oczekuje dwóch parametrów. Jeden to identyfikator koszyka zakupów, a drugi to tablica obiektów typu zdefiniowanego przez użytkownika.

Aby zminimalizować zależność naszej logiki od specyfiki interfejsu użytkownika, zdefiniowaliśmy strukturę danych, która umożliwia przekazanie elementów koszyka do kodu bez konieczności bezpośredniego uzyskiwania dostępu do kontrolki GridView.

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

W naszym pliku MyShoppingCart.aspx.cs możemy użyć tej struktury w procedurze obsługi zdarzeń kliknięcia przycisku aktualizacji w następujący sposób. Pamiętaj, że oprócz aktualizowania koszyka zaktualizujemy również sumę koszyka.

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

Zwróć szczególną uwagę na ten wiersz kodu:

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

GetValues() to specjalna funkcja pomocnika, którą wdrożymy w pliku MyShoppingCart.aspx.cs w następujący sposób.

//--------------------------------------------------------------------------------------+
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;
}

Zapewnia to czysty sposób uzyskiwania dostępu do wartości powiązanych elementów w kontrolce GridView. Ponieważ kontrolka CheckBox "Remove Item" nie jest powiązana, uzyskamy do niej dostęp za pośrednictwem metody FindControl().

Na tym etapie tworzenia projektu przygotowujemy się do wdrożenia procesu wyewidencjonowania.

Przed wykonaniem tej czynności użyjemy programu Visual Studio do wygenerowania bazy danych członkostwa i dodania użytkownika do repozytorium członkostwa.