第 5 部分:业务逻辑

作者 :Joe Stagner

Tailspin Spyworks 演示了为 .NET 平台创建强大且可缩放的应用程序是多么简单。 它展示了如何使用 ASP.NET 4 中的出色新功能来构建在线商店,包括购物、结账和管理。

本教程系列详细介绍了生成 Tailspin Spyworks 示例应用程序所执行的所有步骤。 第 5 部分添加了一些业务逻辑。

添加一些业务逻辑

我们希望每当有人访问我们网站时,我们的购物体验都可用。 访问者将能够浏览商品并将其添加到购物车,即使他们未注册或登录。 当他们准备好检查时,将为其提供身份验证选项,如果尚未成为成员,他们将能够创建帐户。

这意味着我们需要实现逻辑,将购物车从匿名状态转换为“已注册用户”状态。

让我们创建一个名为“Classs”的目录,然后在文件夹上Right-Click,并创建名为 MyShoppingCart.cs 的新“Class”文件

显示名为“我的购物车”点 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)
        {

        }
    }
}

请注意使用“部分”关键字 (keyword) 。

我们刚刚生成的类文件如下所示。

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

namespace TailspinSpyworks.Classes
{
    public class MyShoppingCart
    {
    }
}

我们还将通过将部分关键字 (keyword) 添加到此文件来合并实现。

新类文件现在如下所示。

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>

在设计器中调用窗体,以便双击“更新购物车”按钮,并生成标记的声明中指定的单击事件处理程序。

稍后我们将实现详细信息,但这样做将让我们生成并运行应用程序,而不会出错。

运行应用程序并将项添加到购物车时,会看到此内容。

显示更新的购物车的屏幕截图。

请注意,通过实现三个自定义列,我们偏离了“默认”网格显示。

第一个字段是 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>

显示更新的“数量”和“删除项”的屏幕截图。

如你所看到的,“订单总计”行为空,因此让我们添加一些逻辑来计算订单总计。

首先,我们将对 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);
        }
    }
}

使用基本的“删除”和“更新”功能,我们可以实现实际更新数据库中购物车的逻辑。 (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);
        }            
    }           
}

你会注意到此方法需要两个参数。 一个是购物车 ID,另一个是用户定义的类型的对象的数组。

为了最大程度地减少逻辑对用户界面细节的依赖,我们定义了一个数据结构,可用于将购物车项传递给代码,而无需方法直接访问 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 生成成员资格数据库,并将用户添加到成员资格存储库。