ASP.NET MVC 音乐商店 - 9. 注册和结账

来源:岁月联盟 编辑:exp 时间:2011-11-22

 

在这一节,我们将创建结账的控制器 CheckoutController 来收集用户的地址和付款信息,我们需要用户在结账前注册账户,因为这个控制器需要授权。

当用户点击结账 Checkout 按钮的时候,用户将会被导航到结账的处理流程中。

/

 

如果用户没有登录,将会被提示需要登录。

/

 

一旦用户成功登陆,用户就可以看到地址和付款的视图。

/

 

一旦用户填写了这个表单并提交,他们将会看到订单的确认页面。

/

 

视图访问不存在的订单,或者不属于你的订单,将会看到错误页面。

/

 

 

 

合并购物车

 

在匿名购物的时候,当用户点击结账Checkout 按钮,用户会被要求注册和登陆,用户会希望继续使用原来的购物车,所以,在匿名用户登录之后,我们需要维护购物车。

实际上非常简单,因为ShoppingCart 类已经提供了一个方法,通过当前的用户名来获取购物车中所有的项目,在用户注册登录以后,我们只需要调用这个方法。

打开在成员管理和授权中添加的AccountController 类,增加一个using 来引用MvcMusicStore.Models,然后,增加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;

}

 

然后,修改LonOn 的Post 处理方法,在用户通过验证之后,调用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);

}

 

在Register 的Post 处理方法中,一旦用户成功创建帐户,也进行类似的修改,

//

// 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 控制器

 

在Controller 文件夹上右键,添加一个新的控制器,命名为CheckoutController ,使用空的控制器模板。

 /

首先,在控制器上增加授权的标注 [Authorize],来确定用户必须在登录之后才能访问。

 

 

namespace MvcMusicStore.Controllers

{

   [Authorize]

   public class CheckoutController : Controller

 

注意:这一步很像我们前面在StoreManager 控制器上的工作,但是,在那个时候,我们要求用户必须拥有Administrator 的角色。在结账控制器中,我们不需要用户必须是Administrator ,而是必须登录。

出于简化的考虑,在这个教程中没有处理付款的信息,作为替代,我们允许用户输入一个促销代码,这里促销代码定义在常量PromoCode。

像在StoreController 中一样,在控制器中,我们也需要定义MusicStoreEntities 的字段,将它命名为storeDB,结账的开始部分如下。

using MvcMusicStore.Models;

 

namespace MvcMusicStore.Controllers

{

    [Authorize]

    public class CheckoutController : Controller

    {

        MusicStoreEntities storeDB = new MusicStoreEntities();

        const string PromoCode = "FREE";

 

结账的控制器将包含下面的控制器方法:

AddressAndPayment ( Get ) 用来显示一个用户输入信息的表单

AddressAndPayment ( Post ) 验证用户的输入,处理订单。

Complete 用来在在用户完成订单之后显示,这个视图包含用户的订单账号和确认信息。

首先,将Index 方法改名为AddressAndPayment, 这个Action 方法用来显示结账表单,所以,不需要任何的模型信息。

//

// GET: /Checkout/AddressAndPayment

public ActionResult AddressAndPayment()

{

    return View();

}

 

AddressAndPayment 的Post 处理方法使用我们在StoreManagerController 中类似的模式:如果成功了就完成订单,如果失败了就重新显示表单。

在验证了表单之后,我们将会直接检查促销代码,假设所有的信息都是正确的,我们将会在订单中保存信息,告诉购物车对象完成订单处理,最后,重定向到完成的Complete Action 方法。

 

[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);

    }

}

 

一旦完成了结账处理,用户将被重定向到Complete 方法, 这个Action 方法将会进行简单的检查,在显示订单号之前,检查订单是否属于当前登录的用户。

 

//

// 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 文件夹中的error.cshtml 生成。

完整的CheckoutController 如下所示.

using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

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

        }

 

        [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 控制器的某个Action 中点击鼠标的右键,增加名为AddressAndPayment 的强类型Order 视图,使用编辑模板

 

/

这个视图使用我们在 StoreManager 的 Edit 视图中使用的两个技术:

 

1.使用Html.EditorForModel() 来显示订单模型的字段

2.使用Order 模型的验证标签定义验证规则

我们先使用Html.EditorForModel() 方法,然后,增加额外的输入框用来输入促销码,完成的视图如下。

 

@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" />

}

 

定义订单的验证规则

 

现在,我们的视图已经创建了,类似于专辑,我们定义订单的验证规则。在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 方法上点击右键,增加名为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>

 

更新错误视图

 

项目的默认模板中,包含了定义在/Shared Views 文件夹中的错误页面,可以在整个站点中使用。这个页面仅仅包含简单的信息,也没有使用我们的布局,我们更新一下。

由于这是通用的错误页面,内容非常简单,我们仅仅包含一个提示信息,和用来重做工作的导航到上一个页面的链接。

@{ 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.

 

 

作者 冠军