MVC 音乐商店是介绍,并分步说明了如何使用 ASP.NET MVC 和 Visual Studio 为 web 开发教程应用程序。
MVC 音乐商店是一个轻量级的示例存储实现它卖音乐专辑在线,并实现基本的网站管理、 用户登录,和购物车功能。
这个系列教程详细说明所有为构建 ASP.NET MVC 音乐商店示例应用程序采取的步骤。第 9 部分涵盖注册和结帐。
在本节中,我们将创建 CheckoutController 将收集购物者的地址及付款信息。我们将要求用户注册与我们的网站之前签出,因此,此控制器将需要授权。
用户将从导航到结帐过程其购物车通过单击"签出"按钮。
如果用户没有登录,他们将提示。
在成功登录时显示给用户的然后地址和付款方式视图。
一旦他们有填充该窗体并提交订单后,他们将会显示订单确认屏幕。
试图查看非存在顺序或不属于你的订单将显示视图时出错。
虽然购物过程是匿名的当用户单击签出按钮时,他们将需要注册和登录。用户期望我们将保持其购物车信息之间的访问,所以我们将需要将购物车信息与用户相关联,当他们完成注册或登录。
这是要做,其实很简单我们商城类已有一种方法,将当前购物车中的所有项都关联的用户名。我们只被需要用户完成注册或登录时调用此方法。
打开我们添加了我们被设置成员资格和授权时的AccountController类。添加使用语句引用 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; }
接下来,修改登录后期操作调用 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 使用空的控制器模板的项目中添加一个新的控制器。
首先,添加控制器的类声明,要求用户必须注册在结帐之前上方的授权属性:
namespace MvcMusicStore.Controllers { [Authorize] public class CheckoutController : Controller
注: 这是类似于我们的 StoreManagerController,以前所做的更改,但在这种情况下的授权属性所需的用户是管理员角色中。在签出控制器中,我们要求用户登录但不要求他们必须管理员。
为了简单起见,我们不会处理在本教程中的付款信息。相反,我们允许用户进行查阅使用促销代码。我们将存储使用常量命名并此促销代码。
在 StoreController,我们就会宣布一个字段来保存名为 storeDB 的 MusicStoreEntities 类的实例。为了使 MusicStoreEntities 类的使用,我们将需要添加一条 using 语句为 MvcMusicStore.Models 的命名空间。我们签出控制器的顶部显示下面。
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 中使用: 它将尝试接受表单提交并完成订单,并将重新显示该窗体,如果它失败。
验证窗体输入满足我们验证要求的订单后,我们会直接检查并窗体值。假设一切都正确的我们将用该命令保存更新的信息,告诉商城对象完成订购流程,并将重定向到完成行动。
// // 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 的强类型的命令,并使用编辑模板,如下所示的视图。
此视图会使用两个我们看了构建的 StoreManagerEdit 视图时的技巧:
我们先通过更新窗体代码以使用 Html.EditorForModel(),其次为促销代码是额外的文本框。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" /> }
现在,我们认为设置了,我们将会验证规则为我们顺序模型设置正如我们以前做的专辑模型。模型文件夹上单击鼠标右键并添加一个名为订单类。我们以前用于册页验证属性,我们将还使用一个正则表达式来验证用户的电子邮件地址。
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; } } }
试图提交窗体的缺少或无效的信息现在将显示错误消息,使用客户端验证。
好吧,我们做得最多的结帐过程 ; 艰苦工作的我们只是有几个杂物来完成。我们需要添加两个简单的视图,以及我们需要照顾的切换在登录过程中的购物车信息。
签出完整的视图是很简单,因为它只需要显示订单 id。右键单击完整控制器中的操作,并添加命名的视图作为 int 强类型的完成
现在我们将更新查看代码以显示订单 ID,如下所示。
@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>