Freigeben über


Teil 9: Registrierung und Bezahlvorgang

von Jon Galloway

Der MVC Music Store ist eine Tutorialanwendung, die schrittweise erläutert, wie ASP.NET MVC und Visual Studio für die Webentwicklung verwendet werden.

Der MVC Music Store ist eine einfache Beispielspeicherimplementierung, die Musikalben online verkauft und grundlegende Websiteverwaltung, Benutzeranmeldung und Warenkorbfunktionen implementiert.

In dieser Tutorialreihe werden alle Schritte zum Erstellen der ASP.NET MVC Music Store-Beispielanwendung beschrieben. Teil 9 behandelt Registrierung und Checkout.

In diesem Abschnitt erstellen wir einen CheckoutController, der die Adresse und Zahlungsinformationen des Käufers erfasst. Wir fordern die Benutzer auf, sich vor dem Auschecken bei unserer Website zu registrieren, sodass dieser Controller eine Autorisierung benötigt.

Benutzer navigieren von ihrem Einkaufswagen aus zum Bestellvorgang, indem sie auf die Schaltfläche "Auschecken" klicken.

Screenshot des Music Store-Fensters mit der Checkout-Ansicht mit hervorgehobener Schaltfläche

Wenn der Benutzer nicht angemeldet ist, wird er dazu aufgefordert.

Screenshot des Fensters

Nach erfolgreicher Anmeldung wird dem Benutzer dann die Ansicht Adresse und Zahlung angezeigt.

Screenshot des Music Store-Fensters mit der Adress- und Zahlungsansicht mit Feldern zum Erfassen der Lieferadresse und Zahlungsinformationen.

Nachdem sie das Formular ausgefüllt und die Bestellung übermittelt haben, wird ihnen der Bildschirm für die Bestellbestätigung angezeigt.

Screenshot des Fensters

Wenn Sie versuchen, entweder eine nicht vorhandene Reihenfolge oder eine Bestellung anzuzeigen, die nicht zu Ihnen gehört, wird die Fehleransicht angezeigt.

Screenshot des Music Store-Fensters mit der Fehleransicht, wenn der Benutzer versucht, die Bestellung einer anderen Person oder eine fiktive Bestellung anzuzeigen.

Migrieren des Warenkorbs

Während der Einkaufsvorgang anonym ist, muss sich der Benutzer registrieren und anmelden, wenn er auf die Schaltfläche Auschecken klickt. Die Benutzer erwarten, dass wir ihre Warenkorbinformationen zwischen den Besuchen beibehalten, sodass wir die Warenkorbinformationen einem Benutzer zuordnen müssen, wenn er die Registrierung oder Anmeldung abgeschlossen hat.

Dies ist eigentlich sehr einfach, da unsere ShoppingCart-Klasse bereits über eine Methode verfügt, die alle Elemente im aktuellen Warenkorb mit einem Benutzernamen ordnet. Wir müssen diese Methode nur aufrufen, wenn ein Benutzer die Registrierung oder Anmeldung abgeschlossen hat.

Öffnen Sie die AccountController-Klasse , die wir beim Einrichten von Mitgliedschaft und Autorisierung hinzugefügt haben. Fügen Sie eine using-Anweisung hinzu, die auf MvcMusicStore.Models verweist, und fügen Sie dann die folgende MigrateShoppingCart-Methode hinzu:

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;
}

Ändern Sie als Nächstes die Aktion LogOn post, um MigrateShoppingCart aufzurufen, nachdem der Benutzer überprüft wurde, wie unten gezeigt:

//
// 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);
 }

Nehmen Sie die gleiche Änderung an der Post-Aktion Registrieren vor, unmittelbar nach der erfolgreichen Erstellung des Benutzerkontos:

//
// 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);
 }

Das war's – jetzt wird bei erfolgreicher Registrierung oder Anmeldung automatisch ein anonymer Warenkorb in ein Benutzerkonto übertragen.

Erstellen des CheckoutControllers

Klicken Sie mit der rechten Maustaste auf den Ordner Controller, und fügen Sie dem Projekt mit dem Namen CheckoutController mithilfe der Vorlage Leere Controller einen neuen Controller hinzu.

Screenshot des Fensters

Fügen Sie zunächst das Authorize-Attribut oberhalb der Controller-Klassendeklaration hinzu, damit Benutzer sich vor dem Auschecken registrieren müssen:

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

Hinweis: Dies ähnelt der Änderung, die wir zuvor am StoreManagerController vorgenommen haben, aber in diesem Fall erforderte das Attribut Authorize, dass der Benutzer eine Administratorrolle hat. Im Checkout-Controller muss der Benutzer angemeldet sein, aber nicht, dass er Administratoren ist.

Der Einfachheit halber befassen wir uns in diesem Tutorial nicht mit Zahlungsinformationen. Stattdessen ermöglichen wir Benutzern das Auschecken mit einem Werbecode. Wir speichern diesen Werbecode mithilfe einer Konstanten namens PromoCode.

Wie im StoreController deklarieren wir ein Feld, das eine instance der MusicStoreEntities-Klasse mit dem Namen storeDB enthält. Um die MusicStoreEntities-Klasse verwenden zu können, müssen wir eine using-Anweisung für den MvcMusicStore.Models-Namespace hinzufügen. Der obere Bereich unseres Checkout-Controllers wird unten angezeigt.

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";

Der CheckoutController verfügt über die folgenden Controlleraktionen:

AddressAndPayment (GET-Methode) zeigt ein Formular an, das es dem Benutzer ermöglicht, seine Informationen einzugeben.

AddressAndPayment (POST-Methode) überprüft die Eingabe und verarbeitet den Auftrag.

Abgeschlossen wird angezeigt, nachdem ein Benutzer den Auscheckvorgang erfolgreich abgeschlossen hat. Diese Ansicht enthält die Bestellnummer des Benutzers als Bestätigung.

Zunächst benennen wir die Aktion Indexcontroller (die beim Erstellen des Controllers generiert wurde) in AddressAndPayment um. Diese Controlleraktion zeigt nur das Auscheckformular an, sodass keine Modellinformationen erforderlich sind.

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

Unsere AddressAndPayment POST-Methode folgt demselben Muster, das wir im StoreManagerController verwendet haben: Sie versucht, die Formularübermittlung zu akzeptieren und die Bestellung abzuschließen, und das Formular wird erneut angezeigt, wenn es fehlschlägt.

Nachdem die Formulareingabe unsere Validierungsanforderungen für einen Auftrag erfüllt hat, überprüfen wir den PromoCode-Formularwert direkt. Wenn alles korrekt ist, speichern wir die aktualisierten Informationen mit der Bestellung, weisen das ShoppingCart-Objekt an, den Bestellvorgang abzuschließen, und leiten sie zur Aktion Abschließen um.

//
// 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);
    }
}

Nach erfolgreichem Abschluss des Auscheckvorgangs werden Benutzer zur Aktion Controller abschließen umgeleitet. Diese Aktion führt eine einfache Überprüfung durch, um zu überprüfen, ob die Bestellung tatsächlich dem angemeldeten Benutzer gehört, bevor die Bestellnummer als Bestätigung angezeigt wird.

//
// 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");
    }
}

Hinweis: Die Fehleransicht wurde automatisch für uns im Ordner /Views/Shared erstellt, als wir mit dem Projekt begonnen haben.

Der vollständige CheckoutController-Code lautet wie folgt:

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");
            }
        }
    }
}

Hinzufügen der AddressAndPayment-Ansicht

Nun erstellen wir die Ansicht AddressAndPayment. Klicken Sie mit der rechten Maustaste auf eine der AddressAndPayment-Controlleraktionen, und fügen Sie eine Ansicht mit dem Namen AddressAndPayment hinzu, die stark als Bestellung eingegeben wird und die Vorlage Bearbeiten verwendet, wie unten gezeigt.

Screenshot des Fensters

In dieser Ansicht werden zwei der Techniken verwendet, die wir beim Erstellen der StoreManagerEdit-Ansicht untersucht haben:

  • Wir verwenden Html.EditorForModel(), um Formularfelder für das Order-Modell anzuzeigen.
  • Wir nutzen Validierungsregeln mithilfe einer Order-Klasse mit Validierungsattributen.

Zunächst wird der Formularcode aktualisiert, um Html.EditorForModel() zu verwenden, gefolgt von einem zusätzlichen Textfeld für den Promo-Code. Der vollständige Code für die AddressAndPayment-Ansicht ist unten dargestellt.

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

Definieren von Validierungsregeln für die Bestellung

Nachdem unsere Ansicht eingerichtet wurde, richten wir die Validierungsregeln für unser Order-Modell wie zuvor für das Albummodell ein. Klicken Sie mit der rechten Maustaste auf den Ordner Models, und fügen Sie eine Klasse mit dem Namen Order hinzu. Zusätzlich zu den Validierungsattributen, die wir zuvor für das Album verwendet haben, verwenden wir auch einen regulären Ausdruck, um die E-Mail-Adresse des Benutzers zu überprüfen.

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; }
    }
}

Beim Versuch, das Formular mit fehlenden oder ungültigen Informationen zu übermitteln, wird nun eine Fehlermeldung mithilfe der clientseitigen Validierung angezeigt.

Screenshot des Fensters

Okay, wir haben die meiste harte Arbeit für den Checkout-Prozess geleistet. wir haben nur ein paar Quoten und Enden zu ende. Wir müssen zwei einfache Ansichten hinzufügen, und wir müssen uns um die Übergabe der Warenkorbinformationen während des Anmeldeprozesses kümmern.

Hinzufügen der Ansicht "Auschecken abgeschlossen"

Die Ansicht "Auschecken abgeschlossen" ist ziemlich einfach, da nur die Bestell-ID angezeigt werden muss. Klicken Sie mit der rechten Maustaste auf die Aktion Controller abschließen, und fügen Sie eine Ansicht mit dem Namen Abschließen hinzu, die stark als int typisiert ist.

Screenshot des Fensters

Nun aktualisieren wir den Ansichtscode, um die Bestell-ID anzuzeigen, wie unten gezeigt.

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

Aktualisieren der Fehleransicht

Die Standardvorlage enthält eine Fehleransicht im Ordner Freigegebene Ansichten, sodass sie an anderer Stelle auf der Website wiederverwendet werden kann. Diese Fehleransicht enthält einen sehr einfachen Fehler und verwendet nicht unser Websitelayout, sodass wir es aktualisieren.

Da es sich um eine generische Fehlerseite handelt, ist der Inhalt sehr einfach. Wir fügen eine Nachricht und einen Link zum Navigieren zur vorherigen Seite im Verlauf hinzu, wenn der Benutzer seine Aktion erneut ausprobieren möchte.

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