共用方式為


第 9 部分:註冊和簽出

作者 :JonGaloway

MVC 音樂市集是一個教學課程應用程式,會介紹並說明如何使用 ASP.NET MVC 和 Visual Studio 進行 Web 開發。

MVC 音樂市集是輕量型的市集實作,可線上銷售音樂相簿,並實作基本網站管理、使用者登入和購物車功能。

本教學課程系列詳細說明建置 ASP.NET MVC 音樂市集範例應用程式所採取的所有步驟。 第 9 部分涵蓋註冊和簽出。

在本節中,我們將建立 CheckoutController,以收集購物者的位址和付款資訊。 在簽出之前,我們會要求使用者向網站註冊,因此此控制器將需要授權。

使用者會按一下 [結帳] 按鈕,從購物車流覽至結帳程式。

[音樂市集] 視窗的螢幕擷取畫面,其中顯示結帳檢視,並醒目提示紅色箭號的 [結帳] 按鈕。

如果未登入使用者,系統會提示他們。

[音樂市集] 視窗的螢幕擷取畫面,其中顯示 [使用者名稱] 和 [密碼] 欄位的登入檢視。

成功登入後,使用者會顯示 [位址] 和 [付款] 檢視。

[音樂市集] 視窗的螢幕擷取畫面,其中顯示位址和付款檢視,其中包含要收集寄送位址和付款資訊的欄位。

一旦填寫表單並提交訂單,就會顯示訂單確認畫面。

[音樂市集] 視窗的螢幕擷取畫面,其中顯示結帳完成檢視,通知使用者訂單已完成。

嘗試檢視不存在的順序或不屬於您的順序會顯示 [錯誤] 檢視。

當使用者嘗試檢視另一個人訂單或虛構訂單時,[音樂市集] 視窗的螢幕擷取畫面,其中顯示錯誤檢視。

移轉購物車

雖然購物程式是匿名的,但當使用者按一下 [結帳] 按鈕時,就必須註冊和登入。 使用者預期我們會在造訪之間維護其購物車資訊,因此我們必須在完成註冊或登入時,將購物車資訊與使用者產生關聯。

這實際上很簡單,因為我們的 ShoppingCart 類別已經有方法可將目前購物車中的所有專案與使用者名稱產生關聯。 當使用者完成註冊或登入時,我們只需要呼叫這個方法。

開啟我們在設定成員資格和授權時新增的 AccountController 類別。 新增參考 MvcMusicStore.Models 的 using 語句,然後新增下列 MigrateShoppingCart 方法:

private void MigrateShoppingCart(string UserName)
{
    // Associate shopping cart items with logged-in user
    var cart = ShoppingCart.GetCart(this.HttpContext);
 
    cart.MigrateCart(UserName);
    Session[ShoppingCart.CartSessionKey] = UserName;
}

接下來,修改 LogOn 張貼動作以在驗證使用者之後呼叫 MigrateShoppingCart,如下所示:

//
// POST: /Account/LogOn
[HttpPost]
 public ActionResult LogOn(LogOnModel model, string returnUrl)
 {
    if (ModelState.IsValid)
    {
        if (Membership.ValidateUser(model.UserName, model.Password))
        {
            MigrateShoppingCart(model.UserName);
                    
            FormsAuthentication.SetAuthCookie(model.UserName,
                model.RememberMe);
            if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1
                && returnUrl.StartsWith("/")
                && !returnUrl.StartsWith("//") &&
                !returnUrl.StartsWith("/\\"))
            {
                return Redirect(returnUrl);
            }
            else
            {
                return RedirectToAction("Index", "Home");
            }
        }
        else
        {
            ModelState.AddModelError("", "The user name or password provided is incorrect.");
        }
    }
    // If we got this far, something failed, redisplay form
    return View(model);
 }

在成功建立使用者帳戶之後,對 [註冊後] 動作進行相同的變更:

//
// POST: /Account/Register
[HttpPost]
 public ActionResult Register(RegisterModel model)
 {
    if (ModelState.IsValid)
    {
        // Attempt to register the user
        MembershipCreateStatus createStatus;
        Membership.CreateUser(model.UserName, model.Password, model.Email, 
               "question", "answer", true, null, out
               createStatus);
 
        if (createStatus == MembershipCreateStatus.Success)
        {
            MigrateShoppingCart(model.UserName);
                    
            FormsAuthentication.SetAuthCookie(model.UserName, false /*
                  createPersistentCookie */);
            return RedirectToAction("Index", "Home");
        }
        else
        {
            ModelState.AddModelError("", ErrorCodeToString(createStatus));
        }
    }
    // If we got this far, something failed, redisplay form
    return View(model);
 }

這就是這樣- 現在匿名購物車會在成功註冊或登入時自動轉移至使用者帳戶。

建立 CheckoutController

以滑鼠右鍵按一下 Controllers 資料夾,然後使用空白控制器範本,將新的 Controller 新增至名為 CheckoutController 的專案。

[新增控制器] 視窗的螢幕擷取畫面,其中已填入 [簽出控制器] 文字的 [控制器名稱] 欄位。

首先,在 Controller 類別宣告上方新增 Authorize 屬性,以要求使用者在簽出前註冊:

namespace MvcMusicStore.Controllers
{
    [Authorize]
    public class CheckoutController : Controller

注意:這類似于我們先前對 StoreManagerController 所做的變更,但在此情況下,授權屬性需要使用者處於系統管理員角色。 在簽出控制器中,我們需要使用者登入,但不需要他們是系統管理員。

為了簡單起見,我們不會在本教學課程中處理付款資訊。 相反地,我們會允許使用者使用促銷碼簽出。 我們將使用名為 PromoCode 的常數來儲存此促銷碼。

如同 StoreController,我們將宣告欄位來保存名為 storeDB 的 MusicStoreEntities 類別實例。 為了使用 MusicStoreEntities 類別,我們必須為 MvcMusicStore.Models 命名空間新增 using 語句。 我們的結帳控制器頂端會出現在下方。

using System;
using System.Linq;
using System.Web.Mvc;
using MvcMusicStore.Models;
 
namespace MvcMusicStore.Controllers
{
    [Authorize]
    public class CheckoutController : Controller
    {
        MusicStoreEntities storeDB = new MusicStoreEntities();
        const string PromoCode = "FREE";

CheckoutController 會有下列控制器動作:

AddressAndPayment (GET 方法) 會顯示表單,讓使用者輸入其資訊。

AddressAndPayment (POST 方法) 將會驗證輸入並處理訂單。

使用者 成功完成簽出程式之後,將會顯示完成。 此檢視將包含使用者的訂單號碼作為確認。

首先,讓我們重新命名索引控制器動作, (當我們建立控制器) 至 AddressAndPayment 時所產生的動作。 此控制器動作只會顯示簽出表單,因此不需要任何模型資訊。

//
// GET: /Checkout/AddressAndPayment
public ActionResult AddressAndPayment()
{
    return View();
}

我們的 AddressAndPayment POST 方法會遵循我們在 StoreManagerController 中使用的相同模式:它會嘗試接受表單提交並完成訂單,並在表單失敗時重新顯示表單。

驗證表單輸入符合訂單的驗證需求之後,我們會直接檢查 PromoCode 表單值。 假設一切正確,我們會使用訂單儲存更新的資訊、告訴 ShoppingCart 物件完成訂單程式,然後重新導向至 [完成] 動作。

//
// POST: /Checkout/AddressAndPayment
[HttpPost]
public ActionResult AddressAndPayment(FormCollection values)
{
    var order = new Order();
    TryUpdateModel(order);
 
    try
    {
        if (string.Equals(values["PromoCode"], PromoCode,
            StringComparison.OrdinalIgnoreCase) == false)
        {
            return View(order);
        }
        else
        {
            order.Username = User.Identity.Name;
            order.OrderDate = DateTime.Now;
 
            //Save Order
            storeDB.Orders.Add(order);
            storeDB.SaveChanges();
            //Process the order
            var cart = ShoppingCart.GetCart(this.HttpContext);
            cart.CreateOrder(order);
 
            return RedirectToAction("Complete",
                new { id = order.OrderId });
        }
    }
    catch
    {
        //Invalid - redisplay with errors
        return View(order);
    }
}

在簽出程式成功完成時,系統會將使用者重新導向至 [完成控制器] 動作。 此動作會執行簡單的檢查,以驗證訂單確實屬於已登入的使用者,再將訂單號碼顯示為確認。

//
// GET: /Checkout/Complete
public ActionResult Complete(int id)
{
    // Validate customer owns this order
    bool isValid = storeDB.Orders.Any(
        o => o.OrderId == id &&
        o.Username == User.Identity.Name);
 
    if (isValid)
    {
        return View(id);
    }
    else
    {
        return View("Error");
    }
}

注意:當我們開始專案時,已在 /Views/Shared 資料夾中自動為您建立錯誤檢視。

完整的 CheckoutController 程式碼如下所示:

using System;
using System.Linq;
using System.Web.Mvc;
using MvcMusicStore.Models;
 
namespace MvcMusicStore.Controllers
{
    [Authorize]
    public class CheckoutController : Controller
    {
        MusicStoreEntities storeDB = new MusicStoreEntities();
        const string PromoCode = "FREE";
        //
        // GET: /Checkout/AddressAndPayment
        public ActionResult AddressAndPayment()
        {
            return View();
        }
        //
        // POST: /Checkout/AddressAndPayment
        [HttpPost]
        public ActionResult AddressAndPayment(FormCollection values)
        {
            var order = new Order();
            TryUpdateModel(order);
 
            try
            {
                if (string.Equals(values["PromoCode"], PromoCode,
                    StringComparison.OrdinalIgnoreCase) == false)
                {
                    return View(order);
                }
                else
                {
                    order.Username = User.Identity.Name;
                    order.OrderDate = DateTime.Now;
 
                    //Save Order
                    storeDB.Orders.Add(order);
                    storeDB.SaveChanges();
                    //Process the order
                    var cart = ShoppingCart.GetCart(this.HttpContext);
                    cart.CreateOrder(order);
 
                    return RedirectToAction("Complete",
                        new { id = order.OrderId });
                }
            }
            catch
            {
                //Invalid - redisplay with errors
                return View(order);
            }
        }
        //
        // GET: /Checkout/Complete
        public ActionResult Complete(int id)
        {
            // Validate customer owns this order
            bool isValid = storeDB.Orders.Any(
                o => o.OrderId == id &&
                o.Username == User.Identity.Name);
 
            if (isValid)
            {
                return View(id);
            }
            else
            {
                return View("Error");
            }
        }
    }
}

新增 AddressAndPayment 檢視

現在,讓我們建立 AddressAndPayment 檢視。 以滑鼠右鍵按一下其中一個 AddressAndPayment 控制器動作,並新增名為 AddressAndPayment 的檢視,此檢視強式類型為 Order 並使用編輯範本,如下所示。

[新增檢視] 視窗的螢幕擷取畫面,其中 [檢視名稱] 欄位、[建立檢視] 核取方塊,以及醒目提示紅色的 [模型] 類別和 [Scaffold] 下拉式清單。

此檢視會使用我們在建置 StoreManagerEdit 檢視時所查看的兩種技術:

  • 我們將使用 Html.EditorForModel () 來顯示 Order 模型的表單欄位
  • 我們將使用 Order 類別搭配驗證屬性來運用驗證規則

我們將從更新表單程式碼以使用 Html.EditorForModel () 開始,後面接著 Promo Code 的其他文字方塊。 AddressAndPayment 檢視的完整程式碼如下所示。

@model MvcMusicStore.Models.Order
@{
    ViewBag.Title = "Address And Payment";
}
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")"
type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")"
type="text/javascript"></script>
@using (Html.BeginForm()) {
    
    <h2>Address And Payment</h2>
    <fieldset>
        <legend>Shipping Information</legend>
        @Html.EditorForModel()
    </fieldset>
    <fieldset>
        <legend>Payment</legend>
        <p>We're running a promotion: all music is free 
            with the promo code: "FREE"</p>
        <div class="editor-label">
            @Html.Label("Promo Code")
        </div>
        <div class="editor-field">
            @Html.TextBox("PromoCode")
        </div>
    </fieldset>
    
    <input type="submit" value="Submit Order" />
}

定義 Order 的驗證規則

現在已設定我們的檢視,我們將設定訂單模型的驗證規則,如同先前針對相簿模型所做的一樣。 以滑鼠右鍵按一下 Models 資料夾,然後新增名為 Order 的類別。 除了我們先前用於相簿的驗證屬性之外,我們也會使用正則運算式來驗證使用者的電子郵件地址。

using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
 
namespace MvcMusicStore.Models
{
    [Bind(Exclude = "OrderId")]
    public partial class Order
    {
        [ScaffoldColumn(false)]
        public int OrderId { get; set; }
        [ScaffoldColumn(false)]
        public System.DateTime OrderDate { get; set; }
        [ScaffoldColumn(false)]
        public string Username { get; set; }
        [Required(ErrorMessage = "First Name is required")]
        [DisplayName("First Name")]
        [StringLength(160)]
        public string FirstName { get; set; }
        [Required(ErrorMessage = "Last Name is required")]
        [DisplayName("Last Name")]
        [StringLength(160)]
        public string LastName { get; set; }
        [Required(ErrorMessage = "Address is required")]
        [StringLength(70)]
        public string Address { get; set; }
        [Required(ErrorMessage = "City is required")]
        [StringLength(40)]
        public string City { get; set; }
        [Required(ErrorMessage = "State is required")]
        [StringLength(40)]
        public string State { get; set; }
        [Required(ErrorMessage = "Postal Code is required")]
        [DisplayName("Postal Code")]
        [StringLength(10)]
        public string PostalCode { get; set; }
        [Required(ErrorMessage = "Country is required")]
        [StringLength(40)]
        public string Country { get; set; }
        [Required(ErrorMessage = "Phone is required")]
        [StringLength(24)]
        public string Phone { get; set; }
        [Required(ErrorMessage = "Email Address is required")]
        [DisplayName("Email Address")]
       
        [RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}",
            ErrorMessage = "Email is is not valid.")]
        [DataType(DataType.EmailAddress)]
        public string Email { get; set; }
        [ScaffoldColumn(false)]
        public decimal Total { get; set; }
        public List<OrderDetail> OrderDetails { get; set; }
    }
}

嘗試提交遺漏或無效資訊的表單現在會顯示使用用戶端驗證的錯誤訊息。

[音樂市集] 視窗的螢幕擷取畫面,其中顯示位址和付款檢視,其中包含電話和電子郵件欄位中無效資訊的字串。

沒關係,我們已對結帳程式進行大部分的困難工作;我們只有一些奇數,最後才完成。 我們需要新增兩個簡單的檢視,而且必須在登入程式期間處理購物車資訊的交接。

新增 [簽出完成] 檢視

[簽出完成] 檢視相當簡單,因為它只需要顯示訂單識別碼。 以滑鼠右鍵按一下 [完成控制器] 動作,並新增名為 Complete 的檢視,其強型別為 int。

[新增檢視] 視窗的螢幕擷取畫面,其中 [檢視名稱] 欄位和 [模型] 類別下拉式清單醒目提示在紅色矩形中。

現在,我們將更新檢視程式碼以顯示訂單識別碼,如下所示。

@model int
@{
    ViewBag.Title = "Checkout Complete";
}
<h2>Checkout Complete</h2>
<p>Thanks for your order! Your order number is: @Model</p>
<p>How about shopping for some more music in our 
    @Html.ActionLink("store",
"Index", "Home")
</p>

更新錯誤檢視

預設範本在 [共用檢視] 資料夾中包含 [錯誤] 檢視,以便可在網站的其他位置重複使用。 此錯誤檢視包含非常簡單的錯誤,而且不會使用網站配置,因此我們會更新它。

因為這是一般錯誤頁面,所以內容非常簡單。 如果使用者想要重新嘗試其動作,我們會包含訊息和連結,以流覽至歷程記錄中的上一頁。

@{
    ViewBag.Title = "Error";
}
 
<h2>Error</h2>
 
<p>We're sorry, we've hit an unexpected error.
    <a href="javascript:history.go(-1)">Click here</a> 
    if you'd like to go back and try that again.</p>