Partie 5 : Logique métier
par Joe Stagner
Tailspin Spyworks montre à quel point il est extrêmement simple de créer des applications puissantes et évolutives pour la plateforme .NET. Il montre comment utiliser les nouvelles fonctionnalités de ASP.NET 4 pour créer un magasin en ligne, y compris les achats, les caisses et l’administration.
Cette série de tutoriels détaille toutes les étapes à suivre pour générer l’exemple d’application Tailspin Spyworks. La partie 5 ajoute une logique métier.
Ajout d’une logique métier
Nous voulons que notre expérience d’achat soit disponible chaque fois que quelqu’un visite notre site web. Les visiteurs pourront parcourir et ajouter des éléments au panier même s’ils ne sont pas inscrits ou connectés. Lorsqu’ils sont prêts à case activée, ils ont la possibilité de s’authentifier et, s’ils ne sont pas encore membres, ils peuvent créer un compte.
Cela signifie que nous devons implémenter la logique pour convertir le panier d’un état anonyme en un état « Utilisateur inscrit ».
Nous allons créer un répertoire nommé « Classes », puis Right-Click sur le dossier et créer un fichier « Class » nommé MyShoppingCart.cs
Comme mentionné précédemment, nous allons étendre la classe qui implémente la page MyShoppingCart.aspx et nous allons le faire à l’aide de . Puissante construction de « classe partielle » de NET.
L’appel généré pour notre fichier MyShoppingCart.aspx.cf ressemble à ceci.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace TailspinSpyworks
{
public partial class MyShoppingCart : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
}
Notez l’utilisation de la mot clé « partielle ».
Le fichier de classe que nous venons de générer ressemble à ceci.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace TailspinSpyworks.Classes
{
public class MyShoppingCart
{
}
}
Nous allons fusionner nos implémentations en ajoutant également la mot clé partielle à ce fichier.
Notre nouveau fichier de classe ressemble maintenant à ceci.
namespace TailspinSpyworks.Classes
{
public partial class MyShoppingCart
{
}
}
La première méthode que nous allons ajouter à notre classe est la méthode « AddItem ». Il s’agit de la méthode qui sera finalement appelée lorsque l’utilisateur clique sur les liens « Ajouter à l’art » dans les pages Liste des produits et Détails du produit.
Ajoutez ce qui suit aux instructions using en haut de la page.
using TailspinSpyworks.Data_Access;
Et ajoutez cette méthode à la classe MyShoppingCart.
//------------------------------------------------------------------------------------+
public void AddItem(string cartID, int productID, int quantity)
{
using (CommerceEntities db = new CommerceEntities())
{
try
{
var myItem = (from c in db.ShoppingCarts where c.CartID == cartID &&
c.ProductID == productID select c).FirstOrDefault();
if(myItem == null)
{
ShoppingCart cartadd = new ShoppingCart();
cartadd.CartID = cartID;
cartadd.Quantity = quantity;
cartadd.ProductID = productID;
cartadd.DateCreated = DateTime.Now;
db.ShoppingCarts.AddObject(cartadd);
}
else
{
myItem.Quantity += quantity;
}
db.SaveChanges();
}
catch (Exception exp)
{
throw new Exception("ERROR: Unable to Add Item to Cart - " +
exp.Message.ToString(), exp);
}
}
}
Nous utilisons LINQ to Entities pour voir si l’article est déjà dans le panier. Si c’est le cas, nous mettons à jour la quantité de commandes de l’élément. Sinon, nous créons une nouvelle entrée pour l’élément sélectionné
Pour appeler cette méthode, nous allons implémenter une page AddToCart.aspx qui non seulement classe cette méthode, mais affiche ensuite l’achat actuel a=panier après l’ajout de l’élément.
Right-Click sur le nom de la solution dans l’Explorateur de solutions et ajoutez et une nouvelle page nommée AddToCart.aspx comme nous l’avons fait précédemment.
Bien que nous puissions utiliser cette page pour afficher des résultats intermédiaires tels que des problèmes de stock faibles, etc., dans notre implémentation, la page ne s’affiche pas réellement, mais appelle plutôt la logique « Ajouter » et la redirection.
Pour ce faire, nous allons ajouter le code suivant à l’événement Page_Load.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Diagnostics;
namespace TailspinSpyworks
{
public partial class AddToCart : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
string rawId = Request.QueryString["ProductID"];
int productId;
if (!String.IsNullOrEmpty(rawId) && Int32.TryParse(rawId, out productId))
{
MyShoppingCart usersShoppingCart = new MyShoppingCart();
String cartId = usersShoppingCart.GetShoppingCartId();
usersShoppingCart.AddItem(cartId, productId, 1);
}
else
{
Debug.Fail("ERROR : We should never get to AddToCart.aspx
without a ProductId.");
throw new Exception("ERROR : It is illegal to load AddToCart.aspx
without setting a ProductId.");
}
Response.Redirect("MyShoppingCart.aspx");
}
}
}
Notez que nous récupérons le produit à ajouter au panier à partir d’un paramètre QueryString et appelons la méthode AddItem de notre classe.
En supposant qu’aucune erreur n’est rencontrée, le contrôle est passé à la page SHoppingCart.aspx que nous allons implémenter entièrement ensuite. En cas d’erreur, nous lèveons une exception.
Actuellement, nous n’avons pas encore implémenté de gestionnaire d’erreurs global afin que cette exception ne soit pas prise en charge par notre application, mais nous allons résoudre ce problème sous peu.
Notez également l’utilisation de l’instruction Debug.Fail() (disponible via using System.Diagnostics;)
Si l’application s’exécute à l’intérieur du débogueur, cette méthode affiche une boîte de dialogue détaillée avec des informations sur l’état des applications, ainsi que le message d’erreur que nous spécifions.
Lors de l’exécution en production, l’instruction Debug.Fail() est ignorée.
Vous remarquerez dans le code ci-dessus un appel à une méthode dans nos noms de classe de panier d’achat « GetShoppingCartId ».
Ajoutez le code pour implémenter la méthode comme suit.
Notez que nous avons également ajouté des boutons de mise à jour et de paiement, ainsi qu’une étiquette dans laquelle nous pouvons afficher le « total » du panier.
public const string CartId = "TailSpinSpyWorks_CartID";
//--------------------------------------------------------------------------------------+
public String GetShoppingCartId()
{
if (Session[CartId] == null)
{
Session[CartId] = System.Web.HttpContext.Current.Request.IsAuthenticated ?
User.Identity.Name : Guid.NewGuid().ToString();
}
return Session[CartId].ToString();
}
Nous pouvons maintenant ajouter des éléments à notre panier d’achat, mais nous n’avons pas implémenté la logique d’affichage du panier après l’ajout d’un produit.
Ainsi, dans la page MyShoppingCart.aspx, nous allons ajouter un contrôle EntityDataSource et un contrôle GridVire comme suit.
<div id="ShoppingCartTitle" runat="server" class="ContentHead">Shopping Cart</div>
<asp:GridView ID="MyList" runat="server" AutoGenerateColumns="False" ShowFooter="True"
GridLines="Vertical" CellPadding="4"
DataSourceID="EDS_Cart"
DataKeyNames="ProductID,UnitCost,Quantity"
CssClass="CartListItem">
<AlternatingRowStyle CssClass="CartListItemAlt" />
<Columns>
<asp:BoundField DataField="ProductID" HeaderText="Product ID" ReadOnly="True"
SortExpression="ProductID" />
<asp:BoundField DataField="ModelNumber" HeaderText="Model Number"
SortExpression="ModelNumber" />
<asp:BoundField DataField="ModelName" HeaderText="Model Name"
SortExpression="ModelName" />
<asp:BoundField DataField="UnitCost" HeaderText="Unit Cost" ReadOnly="True"
SortExpression="UnitCost"
DataFormatString="{0:c}" />
<asp:TemplateField>
<HeaderTemplate>Quantity</HeaderTemplate>
<ItemTemplate>
<asp:TextBox ID="PurchaseQuantity" Width="40" runat="server"
Text='<%# Bind("Quantity") %>'></asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField>
<HeaderTemplate>Item Total</HeaderTemplate>
<ItemTemplate>
<%# (Convert.ToDouble(Eval("Quantity")) *
Convert.ToDouble(Eval("UnitCost")))%>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField>
<HeaderTemplate>Remove Item</HeaderTemplate>
<ItemTemplate>
<center>
<asp:CheckBox id="Remove" runat="server" />
</center>
</ItemTemplate>
</asp:TemplateField>
</Columns>
<FooterStyle CssClass="CartListFooter"/>
<HeaderStyle CssClass="CartListHead" />
</asp:GridView>
<div>
<strong>
<asp:Label ID="LabelTotalText" runat="server" Text="Order Total : ">
</asp:Label>
<asp:Label CssClass="NormalBold" id="lblTotal" runat="server"
EnableViewState="false">
</asp:Label>
</strong>
</div>
<br />
<asp:imagebutton id="UpdateBtn" runat="server" ImageURL="Styles/Images/update_cart.gif"
onclick="UpdateBtn_Click"></asp:imagebutton>
<asp:imagebutton id="CheckoutBtn" runat="server"
ImageURL="Styles/Images/final_checkout.gif"
PostBackUrl="~/CheckOut.aspx">
</asp:imagebutton>
<asp:EntityDataSource ID="EDS_Cart" runat="server"
ConnectionString="name=CommerceEntities"
DefaultContainerName="CommerceEntities" EnableFlattening="False"
EnableUpdate="True" EntitySetName="ViewCarts"
AutoGenerateWhereClause="True" EntityTypeFilter="" Select=""
Where="">
<WhereParameters>
<asp:SessionParameter Name="CartID" DefaultValue="0"
SessionField="TailSpinSpyWorks_CartID" />
</WhereParameters>
</asp:EntityDataSource>
Appelez le formulaire dans le concepteur afin de pouvoir double-cliquer sur le bouton Mettre à jour le panier et générer le gestionnaire d’événements click spécifié dans la déclaration dans le balisage.
Nous implémenterons les détails ultérieurement, mais cela nous permettra de générer et d’exécuter notre application sans erreurs.
Lorsque vous exécutez l’application et ajoutez un élément au panier, vous voyez cela.
Notez que nous avons dévié de l’affichage de la grille « par défaut » en implémentant trois colonnes personnalisées.
Le premier est un champ « Lié » modifiable pour la quantité :
<asp:TemplateField>
<HeaderTemplate>Quantity</HeaderTemplate>
<ItemTemplate>
<asp:TextBox ID="PurchaseQuantity" Width="40" runat="server"
Text='<%# Bind("Quantity") %>'></asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
La suivante est une colonne « calculée » qui affiche le total de l’élément de ligne (le coût de l’élément est multiplié par la quantité à commander) :
<asp:TemplateField>
<HeaderTemplate>Item Total</HeaderTemplate>
<ItemTemplate>
<%# (Convert.ToDouble(Eval("Quantity")) *
Convert.ToDouble(Eval("UnitCost")))%>
</ItemTemplate>
</asp:TemplateField>
Enfin, nous avons une colonne personnalisée qui contient un contrôle CheckBox que l’utilisateur utilisera pour indiquer que l’élément doit être supprimé du graphique d’achat.
<asp:TemplateField>
<HeaderTemplate>Remove Item</HeaderTemplate>
<ItemTemplate>
<center>
<asp:CheckBox id="Remove" runat="server" />
</center>
</ItemTemplate>
</asp:TemplateField>
Comme vous pouvez le voir, la ligne Order Total est vide. Nous allons donc ajouter une logique pour calculer le total des commandes.
Nous allons d’abord implémenter une méthode « GetTotal » dans notre classe MyShoppingCart.
Dans le fichier MyShoppingCart.cs, ajoutez le code suivant.
//--------------------------------------------------------------------------------------+
public decimal GetTotal(string cartID)
{
using (CommerceEntities db = new CommerceEntities())
{
decimal cartTotal = 0;
try
{
var myCart = (from c in db.ViewCarts where c.CartID == cartID select c);
if (myCart.Count() > 0)
{
cartTotal = myCart.Sum(od => (decimal)od.Quantity * (decimal)od.UnitCost);
}
}
catch (Exception exp)
{
throw new Exception("ERROR: Unable to Calculate Order Total - " +
exp.Message.ToString(), exp);
}
return (cartTotal);
}
}
Ensuite, dans le gestionnaire d’événements Page_Load, nous pouvons appeler notre méthode GetTotal. En même temps, nous allons ajouter un test pour voir si le panier d’achat est vide et ajuster l’affichage en conséquence s’il l’est.
Maintenant, si le panier d’achat est vide, nous obtenons ceci :
Et si ce n’est pas le cas, nous voyons notre total.
Toutefois, cette page n’est pas encore terminée.
Nous avons besoin d’une logique supplémentaire pour recalculer le panier d’achat en supprimant les éléments marqués pour suppression et en déterminant de nouvelles valeurs de quantité, car certains ont peut-être été modifiés dans la grille par l’utilisateur.
Nous allons ajouter une méthode « RemoveItem » à notre classe de panier d’achat dans MyShoppingCart.cs pour gérer le cas lorsqu’un utilisateur marque un élément pour suppression.
//------------------------------------------------------------------------------------+
public void RemoveItem(string cartID, int productID)
{
using (CommerceEntities db = new CommerceEntities())
{
try
{
var myItem = (from c in db.ShoppingCarts where c.CartID == cartID &&
c.ProductID == productID select c).FirstOrDefault();
if (myItem != null)
{
db.DeleteObject(myItem);
db.SaveChanges();
}
}
catch (Exception exp)
{
throw new Exception("ERROR: Unable to Remove Cart Item - " +
exp.Message.ToString(), exp);
}
}
}
À présent, nous allons ajouter une méthode pour gérer les circonstances où un utilisateur modifie simplement la qualité à commander dans GridView.
//--------------------------------------------------------------------------------------+
public void UpdateItem(string cartID, int productID, int quantity)
{
using (CommerceEntities db = new CommerceEntities())
{
try
{
var myItem = (from c in db.ShoppingCarts where c.CartID == cartID &&
c.ProductID == productID select c).FirstOrDefault();
if (myItem != null)
{
myItem.Quantity = quantity;
db.SaveChanges();
}
}
catch (Exception exp)
{
throw new Exception("ERROR: Unable to Update Cart Item - " +
exp.Message.ToString(), exp);
}
}
}
Une fois les fonctionnalités de base supprimer et mettre à jour en place, nous pouvons implémenter la logique qui met réellement à jour le panier d’achat dans la base de données. (Dans MyShoppingCart.cs)
//-------------------------------------------------------------------------------------+
public void UpdateShoppingCartDatabase(String cartId,
ShoppingCartUpdates[] CartItemUpdates)
{
using (CommerceEntities db = new CommerceEntities())
{
try
{
int CartItemCOunt = CartItemUpdates.Count();
var myCart = (from c in db.ViewCarts where c.CartID == cartId select c);
foreach (var cartItem in myCart)
{
// Iterate through all rows within shopping cart list
for (int i = 0; i < CartItemCOunt; i++)
{
if (cartItem.ProductID == CartItemUpdates[i].ProductId)
{
if (CartItemUpdates[i].PurchaseQantity < 1 ||
CartItemUpdates[i].RemoveItem == true)
{
RemoveItem(cartId, cartItem.ProductID);
}
else
{
UpdateItem(cartId, cartItem.ProductID,
CartItemUpdates[i].PurchaseQantity);
}
}
}
}
}
catch (Exception exp)
{
throw new Exception("ERROR: Unable to Update Cart Database - " +
exp.Message.ToString(), exp);
}
}
}
Vous notez que cette méthode attend deux paramètres. L’un est l’ID du panier d’achat et l’autre est un tableau d’objets de type défini par l’utilisateur.
Pour réduire la dépendance de notre logique vis-à-vis des spécificités de l’interface utilisateur, nous avons défini une structure de données que nous pouvons utiliser pour passer les éléments du panier d’achat à notre code sans que notre méthode ait besoin d’accéder directement au contrôle GridView.
public struct ShoppingCartUpdates
{
public int ProductId;
public int PurchaseQantity;
public bool RemoveItem;
}
Dans notre fichier MyShoppingCart.aspx.cs, nous pouvons utiliser cette structure dans notre gestionnaire d’événements click de bouton de mise à jour comme suit. Notez qu’en plus de mettre à jour le panier, nous allons également mettre à jour le total du panier.
//--------------------------------------------------------------------------------------+
protected void UpdateBtn_Click(object sender, ImageClickEventArgs e)
{
MyShoppingCart usersShoppingCart = new MyShoppingCart();
String cartId = usersShoppingCart.GetShoppingCartId();
ShoppingCartUpdates[] cartUpdates = new ShoppingCartUpdates[MyList.Rows.Count];
for (int i = 0; i < MyList.Rows.Count; i++)
{
IOrderedDictionary rowValues = new OrderedDictionary();
rowValues = GetValues(MyList.Rows[i]);
cartUpdates[i].ProductId = Convert.ToInt32(rowValues["ProductID"]);
cartUpdates[i].PurchaseQantity = Convert.ToInt32(rowValues["Quantity"]);
CheckBox cbRemove = new CheckBox();
cbRemove = (CheckBox)MyList.Rows[i].FindControl("Remove");
cartUpdates[i].RemoveItem = cbRemove.Checked;
}
usersShoppingCart.UpdateShoppingCartDatabase(cartId, cartUpdates);
MyList.DataBind();
lblTotal.Text = String.Format("{0:c}", usersShoppingCart.GetTotal(cartId));
}
Notez avec un intérêt particulier cette ligne de code :
rowValues = GetValues(MyList.Rows[i]);
GetValues() est une fonction d’assistance spéciale que nous allons implémenter dans MyShoppingCart.aspx.cs comme suit.
//--------------------------------------------------------------------------------------+
public static IOrderedDictionary GetValues(GridViewRow row)
{
IOrderedDictionary values = new OrderedDictionary();
foreach (DataControlFieldCell cell in row.Cells)
{
if (cell.Visible)
{
// Extract values from the cell
cell.ContainingField.ExtractValuesFromCell(values, cell, row.RowState, true);
}
}
return values;
}
Cela fournit un moyen propre d’accéder aux valeurs des éléments liés dans notre contrôle GridView. Étant donné que notre contrôle CheckBox « Remove Item » n’est pas lié, nous y accéderons via la méthode FindControl().
À ce stade du développement de votre projet, nous nous préparons à implémenter le processus de validation.
Avant cela, nous allons utiliser Visual Studio pour générer la base de données d’appartenances et ajouter un utilisateur au référentiel d’appartenances.