第 5 部分:业务逻辑
作者 :Joe Stagner
Tailspin Spyworks 演示了为 .NET 平台创建强大且可缩放的应用程序是多么简单。 它展示了如何使用 ASP.NET 4 中的出色新功能来构建在线商店,包括购物、结账和管理。
本教程系列详细介绍了生成 Tailspin Spyworks 示例应用程序所执行的所有步骤。 第 5 部分添加了一些业务逻辑。
添加一些业务逻辑
我们希望每当有人访问我们网站时,我们的购物体验都可用。 访问者将能够浏览商品并将其添加到购物车,即使他们未注册或登录。 当他们准备好检查时,将为其提供身份验证选项,如果尚未成为成员,他们将能够创建帐户。
这意味着我们需要实现逻辑,将购物车从匿名状态转换为“已注册用户”状态。
让我们创建一个名为“Classs”的目录,然后在文件夹上Right-Click,并创建名为 MyShoppingCart.cs 的新“Class”文件
如前所述,我们将扩展实现 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 生成成员资格数据库,并将用户添加到成员资格存储库。