MVC專案實踐,在三層架構下實現SportsStore-06,實現購物車

Darren Ji發表於2014-06-28

SportsStore是《精通ASP.NET MVC3框架(第三版)》中演示的MVC專案,在該專案中涵蓋了MVC的眾多方面,包括:使用DI容器、URL優化、導航、分頁、購物車、訂單、產品管理、影象上傳......是不錯的MVC實踐專案,但該專案不是放在多層框架下開發的,離真實專案還有一段距離。本系列將嘗試在多層框架下實現SportsStore專案,並用自己的方式實現一些功能。

 

本篇為系列第六篇,包括:

■ 8、購物車
    □ 8.1 購物車模型 購物車幫助類
    □ 8.2 新增"放入購物車"按鈕
    □ 8.3 顯示購物車內容 新增"移除"按鈕
    □ 8.4 顯示購物車摘要
   

  8、購物車

  8.1 購物車模型 購物車幫助類

從邏輯上講,購物車幫助類不僅僅是針對Product的處理,還需考慮每種Product的數量。我們把這2個因素綜合起來封裝成一個基本單元:

public class CartLine
    {
        public Product Product { get; set; }
        public int Quantity { get; set; }
    }

而購物車幫助類的主要工作就是針對這個CartLine集合的各種處理:新增、移除、計算總價、清空等。

using System.Collections.Generic;
using System.Linq;
using MySportsStore.Model;

namespace MySportsStore.WebUI.Models
{
    public class Cart
    {
        private List<CartLine> lineCollection = new List<CartLine>();

        //新增
        public void AddItem(Product product, int quantity)
        {
            CartLine line = lineCollection.Where(p => p.Product.Id == product.Id).FirstOrDefault();
            if (line == null)
            {
                lineCollection.Add(new CartLine(){Product = product, Quantity = quantity});
            }
            else
            {
                line.Quantity += quantity;
            }
        }

        //移除
        public void RemoveLine(Product product)
        {
            lineCollection.RemoveAll(p => p.Product.Id == product.Id);
        }

        //計算總價
        public decimal ComputeTotalValue()
        {
            return lineCollection.Sum(p => p.Product.Price*p.Quantity);
        }

        //清空
        public void Clear()
        {
            lineCollection.Clear();
        }

        //獲取
        public IEnumerable<CartLine> Lines
        {
            get { return lineCollection; }
        }
    }
}

 

  8.2 新增"放入購物車"按鈕

在顯示產品的部分檢視中,新增"放入購物車"按鈕,把Produect的Id和當前頁的URL作為隱藏域傳遞給控制器方法:

@model MySportsStore.Model.Product

<div class="item">
    <h3>@Model.Name</h3>
    @Model.Description
    
   @using (Html.BeginForm("AddToCart", "Cart"))
   {
       @Html.HiddenFor(p => p.Id)
       @Html.Hidden("returnUrl", Request.Url.PathAndQuery)
       <input type="submit" value="+放入購物車"/>
   }

    <h4>@Model.Price.ToString("c")</h4>
</div>

在即將建立的Cart控制器方法中,肯定要用到Cart類的例項,而該例項將貫穿於頁面之間,所以應該存放於Session中。首先想到的是通過如下方式獲取Cart類的例項:

private Cart GetCart()
        {
            Cart cart = (Cart)Session["Cart"];
            if (cart == null)
            {
                cart = new Cart();
                Session["Cart"] = cart;
            }
            return cart;
        }

也可以自定義一個ModelBinder,使之從Session中獲取Cart類的例項:

using System.Web.Mvc;
using MySportsStore.WebUI.Models;

namespace MySportsStore.WebUI.Extension
{
    public class CartModelBinder : IModelBinder
    {
        private const string sessionKey = "Cart";
        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            Cart cart = (Cart)controllerContext.HttpContext.Session[sessionKey];
            if (cart == null)
            {
                cart = new Cart();
                controllerContext.HttpContext.Session[sessionKey] = cart;
            }
            return cart;
        }
    }
}

在全域性檔案Global.asax中把自定義的ModelBinder註冊到MVC中:

protected void Application_Start()
        {
            ......

            ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory());
            ModelBinders.Binders.Add(typeof(Cart), new CartModelBinder());
        }

有了如上鋪墊,在Cart控制器的方法中引數中,可以有一個型別為Cart的引數,該引數值直接從自定義的CartModelBinder中獲取:

using System.Linq;
using System.Web.Mvc;
using MySportsStore.IBLL;
using MySportsStore.Model;
using MySportsStore.WebUI.Models;
using Ninject;

namespace MySportsStore.WebUI.Controllers
{
    public class CartController : BaseController
    {
        [Inject]
        public IProductService ProductService { get; set; }

        public CartController()
        {
            this.AddDisposableObject(ProductService);
        }

        public ActionResult Index(Cart cart, string returnUrl)
        {
            return View(new CartIndexViewModel
            {
                //Cart = GetCart(),
                Cart = cart,
                ReturnUrl = returnUrl
            });
        }

        //新增到購物車
        public RedirectToRouteResult AddToCart(Cart cart, int Id, string returnUrl)
        {
            Product product = ProductService.LoadEntities(p => p.Id == Id).FirstOrDefault();
            if (product != null)
            {
                //GetCart().AddItem(product, 1);
                cart.AddItem(product, 1);
            }
            return RedirectToAction("Index", new {returnUrl});
        }

        //從購物車移除
        public RedirectToRouteResult RemoveFromCart(Cart cart, int Id, string returnUrl)
        {
            Product product = ProductService.LoadEntities(p => p.Id == Id).FirstOrDefault();
            if (product != null)
            {
                //GetCart().RemoveLine(product);
                cart.RemoveLine(product);
            }
            return RedirectToAction("Index", new {returnUrl});
        }

        public ViewResult Summary(Cart cart)
        {
            return View(cart);
        }

        private Cart GetCart()
        {
            Cart cart = (Cart)Session["Cart"];
            if (cart == null)
            {
                cart = new Cart();
                Session["Cart"] = cart;
            }
            return cart;
        }

    }
}

執行:

15

 

  顯示購物車內容 新增"移除"按鈕

在顯示購物車內容檢視頁,除了要把購物車內容呈現,在其對應的檢視模型中還必須有一個屬性,用來存放先前的URL,然後點選頁面的"繼續購物"按鈕,方才可以回到先前的介面。


顯示購物車內容檢視頁的View Model為:

namespace MySportsStore.WebUI.Models
{
    public class CartIndexViewModel
    {
        public Cart Cart { get; set; }
        public string ReturnUrl { get; set; }
    }
}

Cart/Index.cshtml檢視:

@model MySportsStore.WebUI.Models.CartIndexViewModel

@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<table width="50%" align="left">
    <thead>
        <tr>
            <th align="left">產品名稱</th>
            <th align="center">數量</th>          
            <th align="right">單價</th>
            <th align="right">小計</th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var line in Model.Cart.Lines)
        {
            <tr>
                <td align="left">@line.Product.Name</td>
                <td align="center">@line.Quantity</td>
                <td align="right">@line.Product.Price.ToString("c")</td>
                <td align="right">@((line.Quantity * line.Product.Price).ToString("c"))</td>
                <td>
                    @using (Html.BeginForm("RemoveFromCart", "Cart"))
                    {
                        @Html.Hidden("Id", line.Product.Id)
                        @Html.HiddenFor(x => x.ReturnUrl)
                        <input class="actionButtons" type="submit" value="移除"/>
                    }
                </td>
            </tr>
        }
    </tbody>
    <tfoot>
        <tr>
            <td colspan="3" align="right">總計:</td>
            <td align="right">@Model.Cart.ComputeTotalValue().ToString("c")</td>
        </tr>
    </tfoot>
</table>
<p align="left" class="actionButtons" style="width: 100%;clear: both">
    <a href="@Model.ReturnUrl">繼續購物</a>
</p>

執行:

16

 

  8.4 顯示購物車摘要

購物車摘要通常放在頁面的公共部分,用來顯示買了多少件商品,花了多少錢,並提供一個指向購物車顯示頁的連結,以部分檢視的形式存在:

@model MySportsStore.WebUI.Models.Cart

@{
    Layout = null;
}

<div id="cart">
    <span class="caption">
        <b>購物車明細:</b>
        @Model.Lines.Sum(x => x.Quantity) 件,
        @Model.ComputeTotalValue().ToString("c")
    </span>
    
    @Html.ActionLink("結算", "Index", "Cart", new {returnUrl = Request.Url.PathAndQuery}, null)
</div>

然後把這塊部分檢視放到頁面的公共部分,即Views/Shared/_Layout.cshtml中:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
</head>
    <body>
        <div id="header" style="height: 200px;">
             @{Html.RenderAction("Summary", "Cart");}
            <div class="title">體育用品商店</div>
        </div>
        <div id="categories">
            @{Html.RenderAction("Menu","Nav");}
        </div>
        <div id="content">
            @RenderBody()
        </div>
       
        @Scripts.Render("~/bundles/jquery")
        @RenderSection("scripts", required: false)
    </body>
</html>

執行:

17

至此,購物車功能結束。

原始碼在這裡

 

“MVC專案實踐,在三層架構下實現SportsStore”系列包括:

MVC專案實踐,在三層架構下實現SportsStore,從類圖看三層架構

MVC專案實踐,在三層架構下實現SportsStore-01,EF Code First建模、DAL層等

MVC專案實踐,在三層架構下實現SportsStore-02,DbSession層、BLL層

MVC專案實踐,在三層架構下實現SportsStore-03,Ninject控制器工廠等

MVC專案實踐,在三層架構下實現SportsStore-04,實現分頁

MVC專案實踐,在三層架構下實現SportsStore-05,實現導航

MVC專案實踐,在三層架構下實現SportsStore-06,實現購物車

MVC專案實踐,在三層架構下實現SportsStore-07,實現訂單提交

MVC專案實踐,在三層架構下實現SportsStore-08,部署到IIS伺服器

MVC專案實踐,在三層架構下實現SportsStore-09,ASP.NET MVC呼叫ASP.NET Web API的查詢服務

MVC專案實踐,在三層架構下實現SportsStore-10,連線字串的加密和解密

MVC專案實踐,在三層架構下實現SportsStore-11,使用Knockout實現增刪改查

相關文章