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

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 转自http://www.cnblogs.com/haogj/archive/2011/11/20/2255675.html 在这一节,我们将创建结账的控制器 CheckoutController 来收集用户的地址和付款信息,我们需要用户在结账前注册账户,因为这个控制器需要授权。

转自http://www.cnblogs.com/haogj/archive/2011/11/20/2255675.html

 

在这一节,我们将创建结账的控制器 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.
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
3月前
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
42 0
|
1月前
|
开发框架 前端开发 .NET
进入ASP .net mvc的世界
进入ASP .net mvc的世界
29 0
|
1月前
mvc.net分页查询案例——mvc-paper.css
mvc.net分页查询案例——mvc-paper.css
5 0
|
1月前
|
开发框架 前端开发 .NET
C# .NET面试系列六:ASP.NET MVC
<h2>ASP.NET MVC #### 1. MVC 中的 TempData\ViewBag\ViewData 区别? 在ASP.NET MVC中,TempData、ViewBag 和 ViewData 都是用于在控制器和视图之间传递数据的机制,但它们有一些区别。 <b>TempData:</b> 1、生命周期 ```c# TempData 的生命周期是短暂的,数据只在当前请求和下一次请求之间有效。一旦数据被读取,它就会被标记为已读,下一次请求时就会被清除。 ``` 2、用途 ```c# 主要用于在两个动作之间传递数据,例如在一个动作中设置 TempData,然后在重定向到另
100 5
|
3月前
|
XML 前端开发 定位技术
C#(NET Core3.1 MVC)生成站点地图(sitemap.xml)
C#(NET Core3.1 MVC)生成站点地图(sitemap.xml)
25 0
|
3月前
|
前端开发
.net core mvc获取IP地址和IP所在地(其实是百度的)
.net core mvc获取IP地址和IP所在地(其实是百度的)
124 0
|
5月前
|
开发框架 自然语言处理 前端开发
基于ASP.NET MVC开发的、开源的个人博客系统
基于ASP.NET MVC开发的、开源的个人博客系统
52 0
|
8月前
|
SQL 开发框架 前端开发
[回馈]ASP.NET Core MVC开发实战之商城系统(完:内附源码)
经过一段时间的准备,【ASP.NET Core MVC开发实战之商城系统】已经完成,目前代码已开发完成,先将全部内容整理分享,如有不足之处,还请指正。
107 0
|
JavaScript 前端开发 .NET
ASP.NET中注册客户端脚本的三种方式
1. RegisterClientScriptBlock   把Javascript函数放在页面顶部 代码如下: protected void Page_Load(object sender, EventArgs e){  string myScript = @"function Ale...
832 0
|
8月前
|
存储 开发框架 前端开发
[回馈]ASP.NET Core MVC开发实战之商城系统(五)
经过一段时间的准备,新的一期【ASP.NET Core MVC开发实战之商城系统】已经开始,在之前的文章中,讲解了商城系统的整体功能设计,页面布局设计,环境搭建,系统配置,及首页【商品类型,banner条,友情链接,降价促销,新品爆款】,商品列表页面,商品详情等功能的开发,今天继续讲解购物车功能开发,仅供学习分享使用,如有不足之处,还请指正。
117 0

相关实验场景

更多