IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

MVC Music Store


précédentsommairesuivant

XXXIV. Introduction

Dans cette section, nous allons créer un CheckoutController qui va recueillir les informations concernant l'adresse et le paiement de l'acheteur. Nous allons demander aux utilisateurs de s'inscrire sur notre site avant les vérifications, donc le contrôleur sera soumis à autorisation.

Les utilisateurs pourront accéder au processus de paiement de leur panier en cliquant sur le bouton « Checkout ».

Image non disponible

Si l'utilisateur n'est pas connecté, il sera invité à le faire.

Image non disponible

Si la connexion réussit, on affiche la vue AddressAndPayment à l'utilisateur.

Image non disponible

Une fois qu'ils ont rempli le formulaire et soumis la commande, ils pourront voir apparaître l'écran de confirmation de la commande.

Image non disponible

Si l'on tente d'afficher la vue pour une commande qui n'existe pas ou qui ne nous appartient pas, la vue Error nous sera retournée.

Image non disponible

XXXV. Migration du Panier

Alors que le processus d'achat est anonyme, lorsque les utilisateurs cliquent sur le bouton Checkout, ils seront tenus de s'inscrire et s'identifier. Les utilisateurs s'attendront à ce que l'on conserve les informations du panier entre deux visites, nous aurons donc besoin d'associer les informations des paniers aux utilisateurs lorsqu'ils se seront enregistrés ou connectés.

C'est vraiment très simple à faire, car notre classe ShoppingCart a déjà une méthode qui va associer tous les éléments du panier courant à un nom d'utilisateur. Nous aurons juste besoin d'appeler cette méthode lorsqu'un utilisateur s'enregistre ou se connecte.

Ouvrez la classe AccountController que nous avons ajoutée quand nous mettions en place le membership et les autorisations. Ajoutez une instruction using qui référence MvcMusicStore.Models, puis ajoutez la méthode suivante MigrateShoppingCart :

 
Sélectionnez
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;
 }

Puis, modifiez l'action LogOn afin d'appeler MigrateShoppingCart une fois que l'utilisateur aura été validé, comme suit :

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

Effectuez la même modification pour l'action Register, immédiatement après que le compte de l'utilisateur ait été créé avec succès :

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

C'est tout - maintenant un panier anonyme sera automatiquement transféré à un compte d'utilisateur lors d'un enregistrement réussi ou une connexion.

XXXVI. Création du CheckoutController

Faites un clic droit sur le dossier Controllers et ajoutez un nouveau contrôleur au projet appelé CheckoutController en utilisant le template « Empty controller ».

Image non disponible

Tout d'abord, ajoutez l'attribut Authorize au-dessus de la déclaration de la classe contrôleur pour obliger les utilisateurs à s'inscrire avant le paiement :

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

Note: Ceci est similaire au changement que nous avons effectué auparavant dans le StoreManagerController, mais dans ce cas l'attribut Authorize nécessite que l'utilisateur soit dans un rôle Administrator. Dans le CheckoutController, nous exigeons que l'utilisateur soit connecté, mais nous n'exigeons pas qu'il soit administrateur.

Par souci de simplicité, nous n'allons pas traiter les informations de paiement dans ce tutoriel. Au lieu de cela, nous allons autoriser les utilisateurs à utiliser un code promo. Nous allons stocker ce code promo en utilisant une constante appelée PromoCode.

Comme dans le StoreController, nous allons déclarer un champ qui sera une instance de la classe MusicStoreEntities, appelé storeDB. Afin d'utiliser la classe MusicStoreEntities, nous aurons besoin d'ajouter une instruction using pour l'espace de noms MvcMusicStore.Models. Le début de notre contrôleur Checkout apparaît comme suit.

 
Sélectionnez
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";

Le CheckoutController aura les actions suivantes :

AddressAndPayment (GET method) va afficher un formulaire qui permet à l'utilisateur d'entrer ses informations.

AddressAndPayment (POST method) va valider les saisies et procéder à la commande.

Complete sera affichée une fois que l'utilisateur aura finalisé avec succès le processus de paiement. Cette vue inclura le numéro de commande de l'utilisateur, comme confirmation.

En premier lieu, renommons l'action Index (qui a été générée lorsque nous avons créé le contrôleur) de AddressAndPayment. Cette action affiche juste le formulaire de paiement, et ne nécessite aucune information sur le modèle.

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

Notre méthode POST AddressAndPayment va suivre le même pattern que celui que nous avons utilisé dans le StoreManagerController : il va essayer d'accepter la soumission du formulaire et remplir la commande, et va ré-afficher le formulaire si cela échoue.

Une fois que la validation des saisies du formulaire répond à nos exigences de validation d'une commande, nous allons vérifier la valeur PromoCode du formulaire directement. En supposant que tout est correct, nous allons sauvegarder les informations mises à jour avec la commande, indiquer à l'objet ShoppingCart de completer le processus de commande, et de rediriger vers l'action Complete.

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

Après avoir réussi le processus de paiement, les utilisateurs vont être redirigés vers l'action Complete du contrôleur. Cette action va effectuer une simple vérification afin de valider que la commande appartient bien à l'utilisateur connecté avant d'afficher le numéro de commande pour confirmation.

 
Sélectionnez
//
// 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 : La vue Error a été automatiquement créée pour nous dans le dossier /Views/Shared lorsque nous avons commencé le projet.

Le code complet du CheckoutController est le suivant :

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

XXXVII. Ajout de la vue AddressAndPayment

Maintenant, nous allons créer la vue AddressAndPayment. Faites un clic droit sur une des actions du contrôleur AddressAndPayment et ajoutez une vue appelée AddressAndPayment qui est fortement typée à une commande et qui utilise le template Edit, comme montré ci-dessous.

Image non disponible

Cette vue se servira de deux des techniques que nous avons observées pendant la construction de la vue StoreManagerEdit :

  • Nous allons utiliser Html.EditorForModel() pour afficher les champs du formulaire pour le modèle Order
  • Nous allons tirer parti des règles de validation en utilisant la classe Order avec les attributs de validation

Nous allons commencer par mettre à jour le code du formulaire en utilisant Html.EditorForModel(), suivi d'une textbox supplémentaire pour le code promo. Le code complet de la vue AddressAndPayment est montré ci-dessous.

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

XXXVIII. Définition des règles de validation pour la commande

Maintenant que notre vue est prête, nous allons mettre en place les règles de validation pour notre modèle Commande comme nous l'avons fait précédemment pour le modèle Album. Faites un clic droit sur le dossier Models et ajoutez une classe appelée Order. En plus des attributs de validation que nous avons auparavant utilisés pour l'album, nous allons également utiliser une expression régulière pour valider l'adresse email de l'utilisateur.

 
Sélectionnez
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; }
    }
}

Le fait de tenter de soumettre le formulaire avec des informations manquantes ou invalides va maintenant afficher un message d'erreur en utilisant la validation côté client.

Image non disponible

Ok, nous avons fait le plus dur du travail pour le processus de paiement ; nous avons juste quelques bricoles à finaliser. Nous avons besoin d'ajouter deux vues simples, et nous avons besoin de prendre soin de la non-intervention des informations du panier durant le processus de connexion.

XXXIX. Ajout de la vue Checkout Complete

La vue Checkout Complete est assez simple, car elle a juste besoin d'afficher l'ID de la commande. Faites un clic droit sur l'action Complete et ajoutez une vue appelée Complete qui est fortement typée en tant qu'entier.

Image non disponible

Maintenant, nous allons mettre à jour le code de la vue pour afficher l'ID de la commande, comme ci-dessous.

 
Sélectionnez
@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>

XL. Mise à jour de la vue Error

Le template de base inclut une vue Error qui se trouve dans le dossier vue Shared afin qu'elle puisse être réutilisée n'importe où dans le site. Cette vue Error contient une très simple erreur et n'utilise pas la mise en page de notre site, donc nous allons la mettre à jour.

Comme il s'agit d'une page d'erreur générique, le contenu est très simple. Nous allons inclure un message et un lien pour accéder à la page précédente de notre historique si les utilisateurs veulent réessayer leur action.

 
Sélectionnez
@{
    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>

Vous pouvez utiliser les discussions disponibles sur http://mvcmusicstore.codeplex.com pour toutes questions ou tous commentaires.


précédentsommairesuivant

Copyright © 2012 Jon Galloway. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts. Droits de diffusion permanents accordés à Developpez LLC.