9. Registration and Checkout
In this section, we will be creating a CheckoutController which will collect the shopper’s address and payment information. We will require users to register with our site prior to checking out, so this controller will require authorization.
Users will navigate to the checkout process from their shopping cart by clicking the “Checkout” button.
If the user is not logged in, they will be prompted to.
Upon successful login, the user is then shown the Address and Payment view.
Once they have filled the form and submitted the order, they will be shown the order confirmation screen.
Attempting to view either a non-existent order or an order that doesn’t belong to you will show the Error view.
Migrating the Shopping Cart
While the shopping process is anonymous, when the user clicks on the Checkout button, they will be required to register and login. Users will expect that we will maintain their shopping cart information between visits, so we will need to associate the shopping cart information with a user when they complete registration or login.
This is actually very simple to do, as our ShoppingCart class already has a method which will associate all the items in the current cart with a username. We will just need to call this method when a user completes registration or login.
Open the AccountController class that we added when we were setting up Membership and Authorization. Add a using statement referencing MvcMusicStore.Models, then add the following MigrateShoppingCart method:
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;
}
Next, modify the LogOn post action to call MigrateShoppingCart after the user has been validated, as shown below:
//
// 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);
}
Make the same change to the Register post action, immediately after the user account is successfully created:
//
// 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);
}
That’s it - now an anonymous shopping cart will be automatically transferred to a user account upon successful registration or login.
Creating the CheckoutController
Right-click on the Controllers folder and add a new Controller to the project named CheckoutController using the Empty controller template.
First, add the Authorize attribute above the Controller class declaration to require users to register before checkout:
namespace MvcMusicStore.Controllers
{
[Authorize]
public class CheckoutController : Controller
Note: This is similar to the change we previously made to the StoreManagerController, but in that case the Authorize attribute required that the user be in an Administrator role. In the Checkout Controller, we’re requiring the user be logged in but aren’t requiring that they be administrators.
For the sake of simplicity, we won’t be dealing with payment information in this tutorial. Instead, we are allowing users to check out using a promotional code. We will store this promotional code using a constant named PromoCode.
As in the StoreController, we’ll declare a field to hold an instance of the MusicStoreEntities class, named storeDB. In order to make use of the MusicStoreEntities class, we will need to add a using statement for the MvcMusicStore.Models namespace. The top of our Checkout controller appears below.
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";
The CheckoutController will have the following controller actions:
AddressAndPayment (GET method) will display a form to allow the user to enter their information.
AddressAndPayment (POST method) will validate the input and process the order.
Complete will be shown after a user has successfully finished the checkout process. This view will include the user’s order number, as confirmation.
First, let’s rename the Index controller action (which was generated when we created the controller) to AddressAndPayment. This controller action just displays the checkout form, so it doesn’t require any model information.
//
// GET: /Checkout/AddressAndPayment
public ActionResult AddressAndPayment()
{
return View();
}
Our AddressAndPayment POST method will follow the same pattern we used in the StoreManagerController: it will try to accept the form submission and complete the order, and will re-display the form if it fails.
After validating the form input meets our validation requirements for an Order, we will check the PromoCode form value directly. Assuming everything is correct, we will save the updated information with the order, tell the ShoppingCart object to complete the order process, and redirect to the Complete action.
//
// 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);
}
}
Upon successful completion of the checkout process, users will be redirected to the Complete controller action. This action will perform a simple check to validate that the order does indeed belong to the logged-in user before showing the order number as a confirmation.
//
// 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");
}
}
Note: The Error view was automatically created for us in the /Views/Shared folder when we began the project.
The complete CheckoutController code is as follows:
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",