共用方式為


第 5 部分:商務邏輯

作者:Joe Stagner

Tailspin Spyworks 示範如何為 .NET 平臺建立強大且可調整的應用程式,非常簡單。 其中顯示如何使用 ASP.NET 4 中的絕佳新功能來建置線上商店,包括購物、結帳和管理。

本教學課程系列詳細說明建置 Tailspin Spyworks 範例應用程式所採取的所有步驟。 第 5 部分會新增一些商務邏輯。

新增一些商務邏輯

我們希望每當有人造訪我們的網站時,都可以使用我們的購物體驗。 即使訪客未註冊或登入,訪客還是能夠流覽並新增專案至購物車。 當他們準備好簽出時,系統會提供驗證的選項,如果它們還不是成員,他們就能夠建立帳戶。

這表示我們必須實作邏輯,將購物車從匿名狀態轉換成「已註冊的使用者」狀態。

讓我們建立名為 「Classes」 的目錄,然後在資料夾上Right-Click,然後建立名為 MyShoppingCart.cs 的新「類別」檔案

顯示名為 My Cart dot C S 之新類別檔案的螢幕擷取畫面。

顯示 [類別] 資料夾內容的螢幕擷取畫面。

如先前所述,我們將擴充實作 MyShoppingCart.aspx 頁面的 類別,我們會使用 來執行此動作。NET 的強大「部分類別」建構。

為 MyShoppingCart.aspx.cf 檔案產生的呼叫看起來像這樣。

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

        }
    }
}

請注意使用 「partial」 關鍵字。

我們剛才產生的類別檔案看起來像這樣。

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

namespace TailspinSpyworks.Classes
{
    public class MyShoppingCart
    {
    }
}

我們也會將 partial 關鍵字新增至此檔案,以合併我們的實作。

我們的新類別檔案現在看起來像這樣。

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

我們將新增至類別的第一個方法是 「AddItem」 方法。 這是當使用者按一下 [產品清單] 和 [產品詳細資料] 頁面上的 [新增至藝術] 連結時,最終會呼叫的方法。

將下列內容附加至頁面頂端的 using 語句。

using TailspinSpyworks.Data_Access;

並將這個方法新增至 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);
      }
   }
}

我們使用LINQ to Entities來查看專案是否已在購物車中。 如果是,我們會更新專案的訂單數量,否則我們會為選取的專案建立新專案

為了呼叫這個方法,我們將實作 AddToCart.aspx 頁面,該頁面不僅將此方法分類,還會在新增專案之後顯示目前的購物 a=cart。

Right-Click方案總管中的方案名稱,並新增名為 AddToCart.aspx 的新頁面,如同我們先前所做的一樣。

雖然我們可以使用此頁面在實作中顯示短期結果,例如低股票問題等等,但頁面不會實際轉譯,而是呼叫「新增」邏輯和重新導向。

為了達成此目的,我們會將下列程式碼新增至 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");
        }
    }
}

請注意,我們會從 QueryString 參數擷取要新增至購物車的產品,並呼叫 類別的 AddItem 方法。

假設沒有發生任何錯誤的控制項會傳遞至 SHoppingCart.aspx 頁面,我們將在下一步完整實作。 如果應該發生錯誤,我們會擲回例外狀況。

我們目前尚未實作全域錯誤處理常式,因此我們的應用程式不會處理此例外狀況,但我們很快就會解決這個問題。

另請注意,使用可透過 提供的語句 Debug.Fail () (using System.Diagnostics;)

應用程式是在偵錯工具內執行,這個方法會顯示詳細的對話方塊,其中包含應用程式狀態的相關資訊,以及我們指定的錯誤訊息。

在生產環境中執行時,會忽略 Debug.Fail () 語句。

您會在上述程式碼中記下我們購物車類別名稱 「GetShoppingCartId」 中方法的呼叫。

新增程式碼以實作 方法,如下所示。

請注意,我們也新增了更新和結帳按鈕,以及可顯示購物車「總計」的標籤。

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

我們現在可以將專案新增至購物車,但尚未實作在新增產品之後顯示購物車的邏輯。

因此,在 MyShoppingCart.aspx 頁面中,我們將新增 EntityDataSource 控制項和 GridVire 控制項,如下所示。

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

在設計工具中呼叫表單,以便按兩下 [更新購物車] 按鈕,並產生標記中宣告中指定的 Click 事件處理常式。

我們稍後會實作詳細資料,但這麼做可讓我們建置並執行應用程式,而不會發生錯誤。

當您執行應用程式並將專案新增至購物車時,您會看到此專案。

顯示已更新購物車的螢幕擷取畫面。

請注意,我們已藉由實作三個自訂資料行,從「預設」方格顯示中脫離。

第一個是 Quantity 的 [可編輯]、[系結] 欄位:

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

最後,我們有一個自訂資料行,其中包含一個 CheckBox 控制項,使用者將會用來指出專案應該從購物圖表中移除。

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

顯示已更新的數量和移除專案的螢幕擷取畫面。

如您所見,Order Total 行是空的,因此讓我們新增一些邏輯來計算 Order Total。

我們會先對 MyShoppingCart 類別實作 「GetTotal」 方法。

在 MyShoppingCart.cs 檔案中,新增下列程式碼。

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

然後在 Page_Load 事件處理常式中,我們可以呼叫 GetTotal 方法。 同時,我們將新增測試,以查看購物車是否空白,並據以調整顯示。

現在,如果購物車是空的,我們就會得到:

顯示空白購物車的螢幕擷取畫面。

如果沒有,我們就會看到我們的總計。

顯示購物車中專案總金額的螢幕擷取畫面。

不過,此頁面尚未完成。

我們需要額外的邏輯來重新計算購物車,方法是移除標示要移除的專案,以及判斷新的數量詞,因為使用者可能已在方格中變更某些值。

讓我們將 「RemoveItem」 方法新增至 MyShoppingCart.cs 中的購物車類別,以處理使用者標示要移除的專案時的情況。

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

現在讓我們廣告一種方法來處理使用者只是變更在 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);
        }
    }
}

有了基本 Remove 和 Update 功能,我們可以實作實際更新資料庫中購物車的邏輯。 (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);
        }            
    }           
}

您會注意到此方法需要兩個參數。 其中一個是購物車識別碼,另一個是使用者定義類型的物件陣列。

為了將邏輯在使用者介面細節上的相依性降到最低,我們已定義資料結構,我們可用來將購物車專案傳遞至程式碼,而不需要直接存取 GridView 控制項的方法。

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

在我們的 MyShoppingCart.aspx.cs 檔案中,我們可以在 [更新按鈕] 按一下事件處理常式中使用這個結構,如下所示。 請注意,除了更新購物車之外,我們也會更新購物車總計。

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

請注意,這一行程式碼特別感興趣:

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

GetValues () 是特殊的協助程式函式,我們將在 MyShoppingCart.aspx.cs 中實作,如下所示。

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

這可讓您清楚存取 GridView 控制項中系結元素的值。 由於我們的 「移除專案」CheckBox 控制項未系結,因此我們會透過 FindControl () 方法來存取它。

在專案的開發階段中,我們已準備好實作結帳程式。

這麼做之前,讓我們使用 Visual Studio 來產生成員資格資料庫,並將使用者新增至成員資格存放庫。