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 ».
Si l'utilisateur n'est pas connecté, il sera invité à le faire.
Si la connexion réussit, on affiche la vue AddressAndPayment à l'utilisateur.
Une fois qu'ils ont rempli le formulaire et soumis la commande, ils pourront voir apparaître l'écran de confirmation de la commande.
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.
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 :
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 :
//
// 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 :
//
// 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 ».
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 :
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.
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.
//
// 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.
//
// 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.
//
// 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 :
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.
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.
@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.
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.
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.
Maintenant, nous allons mettre à jour le code de la vue pour afficher l'ID de la commande, comme ci-dessous.
@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.
@{
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.