Część 9. Rejestracja i finalizacja zakupu

Autor: Jon Galloway

Sklep MVC Music Store to aplikacja samouczka, która wprowadza i wyjaśnia krok po kroku, jak używać ASP.NET MVC i Visual Studio na potrzeby tworzenia aplikacji internetowych.

MVC Music Store to uproszczona przykładowa implementacja sklepu, która sprzedaje albumy muzyczne online i implementuje podstawową administrację witryną, logowanie użytkowników i funkcjonalność koszyka zakupów.

W tej serii samouczków szczegółowo przedstawiono wszystkie kroki, które należy wykonać w celu utworzenia przykładowej aplikacji ASP.NET MVC Music Store. Część 9 obejmuje rejestrację i wyewidencjonowania.

W tej sekcji utworzymy kontroler checkoutcontroller, który będzie zbierać adres i informacje o płatności kupujących. Będziemy wymagać od użytkowników zarejestrowania się w naszej witrynie przed wyewidencjonowywaniem, więc ten kontroler będzie wymagał autoryzacji.

Użytkownicy będą przechodzić do procesu wyewidencjonowania z koszyka zakupowego, klikając przycisk "Wyewidencjonuj".

Zrzut ekranu przedstawiający okno Sklep muzyczny z widokiem wyewidencjonowania z przyciskiem wyewidencjonowania wyróżnionym czerwoną strzałką.

Jeśli użytkownik nie jest zalogowany, zostanie wyświetlony monit.

Zrzut ekranu przedstawiający okno Sklep muzyczny z widokiem logowania z polami Nazwa użytkownika i Hasło.

Po pomyślnym zalogowaniu użytkownik jest następnie wyświetlany w widoku Adres i Płatność.

Zrzut ekranu przedstawiający okno sklepu muzycznego z polami adresu i widoku płatności w celu zbierania adresów wysyłkowych i informacji o płatności.

Po wypełnieniu formularza i przesłaniu zamówienia zostaną one wyświetlone na ekranie potwierdzenia zamówienia.

Zrzut ekranu przedstawiający okno Sklep muzyczny z pełnym widokiem wyewidencjonowania, który informuje użytkownika o zakończeniu zamówienia.

Próba wyświetlenia nieistniejącej kolejności lub zamówienia, które nie należy do Ciebie, spowoduje wyświetlenie widoku Błąd.

Zrzut ekranu przedstawiający okno Sklep muzyczny z widokiem błędu, gdy użytkownik próbuje wyświetlić zamówienie innej osoby lub fikcyjne zamówienie.

Migrowanie koszyka

Gdy proces zakupów jest anonimowy, gdy użytkownik kliknie przycisk Wyewidencjonuj, będzie musiał się zarejestrować i zalogować. Użytkownicy będą oczekiwać, że będziemy utrzymywać informacje o koszyku zakupów między wizytami, dlatego będziemy musieli skojarzyć informacje o koszyku z użytkownikiem po zakończeniu rejestracji lub zalogowania.

Jest to faktycznie bardzo proste, ponieważ nasza klasa ShoppingCart ma już metodę, która skojarzy wszystkie elementy w bieżącym koszyku z nazwą użytkownika. Wystarczy wywołać tę metodę, gdy użytkownik ukończy rejestrację lub identyfikator logowania.

Otwórz klasę AccountController dodaną podczas konfigurowania członkostwa i autoryzacji. Dodaj instrukcję using odwołującą się do mvcMusicStore.Models, a następnie dodaj następującą metodę 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;
}

Następnie zmodyfikuj akcję wpisu LogOn, aby wywołać obiekt MigrateShoppingCart po zweryfikowaniu użytkownika, jak pokazano poniżej:

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

Wprowadź tę samą zmianę w akcji Zarejestruj wpis natychmiast po pomyślnym utworzeniu konta użytkownika:

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

To wszystko — teraz anonimowy koszyk na zakupy zostanie automatycznie przeniesiony na konto użytkownika po pomyślnej rejestracji lub zalogowaniu.

Tworzenie kontrolera CheckoutController

Kliknij prawym przyciskiem myszy folder Controllers i dodaj nowy kontroler do projektu o nazwie CheckoutController przy użyciu szablonu Pusty kontroler.

Zrzut ekranu przedstawiający okno Dodawanie kontrolera z polem Nazwa kontrolera wypełnionym tekstem Kontroler wyewidencjonowania.

Najpierw dodaj atrybut Authorize powyżej deklaracji klasy Controller, aby wymagać od użytkowników zarejestrowania się przed wyewidencjonowania:

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

Uwaga: jest to podobne do zmiany wprowadzonej wcześniej w StoreManagerController, ale w takim przypadku atrybut Authorize wymaga, aby użytkownik był w roli Administrator. W kontrolerze wyewidencjonowania wymagane jest zalogowanie użytkownika, ale nie wymaga, aby byli administratorami.

Dla uproszczenia nie będziemy mieć do czynienia z informacjami o płatności w tym samouczku. Zamiast tego pozwalamy użytkownikom na wyewidencjonowanie przy użyciu kodu promocyjnego. Będziemy przechowywać ten kod promocyjny przy użyciu stałej o nazwie PromoCode.

Podobnie jak w storeController, zadeklarujemy pole do przechowywania wystąpienia klasy MusicStoreEntities o nazwie storeDB. Aby móc korzystać z klasy MusicStoreEntities, należy dodać instrukcję using dla przestrzeni nazw MvcMusicStore.Models. Poniżej znajduje się górna część kontrolera wyewidencjonowania.

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

Kontroler wyewidencjonowania będzie miał następujące akcje kontrolera:

Metoda AddressAndPayment (GET) wyświetli formularz umożliwiający użytkownikowi wprowadzanie informacji.

AddressAndPayment (metoda POST) zweryfikuje dane wejściowe i przetworzy zamówienie.

Ukończenie zostanie wyświetlone po pomyślnym zakończeniu procesu wyewidencjonowania przez użytkownika. Ten widok będzie zawierać numer zamówienia użytkownika jako potwierdzenie.

Najpierw zmieńmy nazwę akcji kontrolera indeksu (która została wygenerowana podczas tworzenia kontrolera) na AddressAndPayment. Ta akcja kontrolera po prostu wyświetla formularz wyewidencjonowania, więc nie wymaga żadnych informacji o modelu.

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

Nasza metoda AddressAndPayment POST będzie działać zgodnie z tym samym wzorcem, którego użyliśmy w StoreManagerController: podejmie próbę zaakceptowania przesłania formularza i ukończenia zamówienia i ponownie wyświetli formularz, jeśli zakończy się niepowodzeniem.

Po zweryfikowaniu danych wejściowych formularza spełnia nasze wymagania dotyczące walidacji zamówienia, sprawdzimy bezpośrednio wartość formularza PromoCode. Zakładając, że wszystko jest poprawne, zapiszemy zaktualizowane informacje z zamówieniem, poinformuj obiekt ShoppingCart, aby ukończyć proces zamówienia, i przekierowujemy do akcji Zakończ.

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

Po pomyślnym zakończeniu procesu wyewidencjonowania użytkownicy zostaną przekierowani do akcji Zakończ kontroler. Ta akcja spowoduje wykonanie prostego sprawdzenia, czy zamówienie rzeczywiście należy do zalogowanego użytkownika przed wyświetleniem numeru zamówienia jako potwierdzenia.

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

Uwaga: Widok błędu został automatycznie utworzony dla nas w folderze /Views/Shared po rozpoczęciu projektu.

Kompletny kod CheckoutController jest następujący:

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

Dodawanie widoku AddressAndPayment

Teraz utwórzmy widok AddressAndPayment. Kliknij prawym przyciskiem myszy jedną z akcji kontrolera AddressAndPayment i dodaj widok o nazwie AddressAndPayment, który jest silnie typizowane jako zamówienie i używa szablonu Edytuj, jak pokazano poniżej.

Zrzut ekranu przedstawiający okno Dodawanie widoku z polem Nazwa widoku, polem wyboru Utwórz widok oraz listami rozwijanymi Klasa modelu i Szkielet wyróżnione na czerwono.

Ten widok będzie korzystał z dwóch technik, które omówiliśmy podczas kompilowania widoku StoreManagerEdit:

  • Użyjemy metody Html.EditorForModel() do wyświetlania pól formularza dla modelu Order
  • Użyjemy reguł weryfikacji przy użyciu klasy Order z atrybutami weryfikacji

Zaczniemy od zaktualizowania kodu formularza w celu użycia metody Html.EditorForModel(), a następnie dodatkowego pola tekstowego kodu promocyjnego. Poniżej przedstawiono kompletny kod widoku 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" />
}

Definiowanie reguł walidacji dla zamówienia

Teraz, po skonfigurowaniu widoku, skonfigurujemy reguły walidacji dla naszego modelu Zamówienia, tak jak wcześniej dla modelu Album. Kliknij prawym przyciskiem myszy folder Models i dodaj klasę o nazwie Order. Oprócz atrybutów weryfikacji użytych wcześniej dla albumu będziemy również używać wyrażenia regularnego do sprawdzania poprawności adresu e-mail użytkownika.

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

Próba przesłania formularza z brakującymi lub nieprawidłowymi informacjami spowoduje teraz wyświetlenie komunikatu o błędzie przy użyciu weryfikacji po stronie klienta.

Zrzut ekranu przedstawiający okno Sklep muzyczny z adresem i widokiem płatności z ciągiem nieprawidłowych informacji w polach telefonu i poczty e-mail.

Dobrze, wykonaliśmy większość ciężkiej pracy dla procesu wyewidencjonowania; Mamy tylko kilka kursów i kończy się do końca. Musimy dodać dwa proste widoki i musimy zadbać o przekazanie informacji o koszyku podczas procesu logowania.

Dodawanie widoku Finalizacja wyewidencjonowania

Widok Finalizacja wyewidencjonowania jest dość prosty, ponieważ wystarczy wyświetlić identyfikator zamówienia. Kliknij prawym przyciskiem myszy akcję Zakończ kontroler i dodaj widok o nazwie Ukończ, który jest silnie typizowane jako int.

Zrzut ekranu przedstawiający okno Dodawanie widoku z polem Nazwa widoku i listą rozwijaną Klasa modelu wyróżnioną czerwonymi prostokątami.

Teraz zaktualizujemy kod widoku, aby wyświetlić identyfikator zamówienia, jak pokazano poniżej.

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

Aktualizowanie widoku błędu

Szablon domyślny zawiera widok Błąd w folderze Widoki udostępnione, aby można było go ponownie używać w innym miejscu w witrynie. Ten widok błędu zawiera bardzo prosty błąd i nie używa naszego układu witryny, dlatego zaktualizujemy go.

Ponieważ jest to ogólna strona błędu, zawartość jest bardzo prosta. Dołączymy komunikat i link umożliwiający przejście do poprzedniej strony w historii, jeśli użytkownik chce ponowić próbę wykonania akcji.

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