Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
di Joe Stagner
Tailspin Spyworks dimostra quanto sia straordinariamente semplice creare applicazioni potenti e scalabili per la piattaforma .NET. Mostra come usare le nuove funzionalità di ASP.NET 4 per creare un negozio online, tra cui shopping, checkout e amministrazione.
Questa serie di esercitazioni illustra in dettaglio tutti i passaggi eseguiti per compilare l'applicazione di esempio Tailspin Spyworks. La parte 5 aggiunge una logica di business.
Aggiunta di una logica aziendale
Vogliamo che la nostra esperienza di acquisto sia disponibile ogni volta che qualcuno visita il nostro sito Web. I visitatori potranno esplorare e aggiungere articoli al carrello anche se non sono registrati o connessi. Quando sono pronti a eseguire il check-out, gli verrà data la possibilità di eseguire l'autenticazione e, se non sono ancora membri, potranno creare un account.
Ciò significa che sarà necessario implementare la logica per convertire il carrello acquisti da uno stato anonimo a uno stato "Utente registrato".
Creare una directory denominata "Classi", quindi Right-Click nella cartella e creare un nuovo file "Class" denominato MyShoppingCart.cs
Come accennato in precedenza, estenderemo la classe che implementa la pagina MyShoppingCart.aspx e faremo ciò utilizzando il potente costrutto "Partial Class" di .NET.
La chiamata generata per il file di MyShoppingCart.aspx.cf è simile alla seguente.
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)
{
}
}
}
Si noti l'uso della parola chiave (keyword) "partial".
Il file di classe appena generato è simile al seguente.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace TailspinSpyworks.Classes
{
public class MyShoppingCart
{
}
}
Le implementazioni verranno unite aggiungendo anche la parola chiave parziale a questo file.
Il nuovo file di classe avrà ora un aspetto simile al seguente.
namespace TailspinSpyworks.Classes
{
public partial class MyShoppingCart
{
}
}
Il primo metodo che verrà aggiunto alla classe è il metodo "AddItem". Questo è il metodo che verrà chiamato quando l'utente fa clic sui collegamenti "Aggiungi all'Arte" nelle pagine Lista Prodotti e Dettagli Prodotto.
Aggiungere quanto segue alle istruzioni di using nella parte superiore della pagina.
using TailspinSpyworks.Data_Access;
Aggiungere questo metodo alla 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);
}
}
}
Viene usato LINQ to Entities per verificare se l'elemento è già presente nel carrello. In tal caso, aggiorniamo la quantità di ordine dell'articolo; in caso contrario, creiamo una nuova voce per l'articolo selezionato
Per chiamare questo metodo, implementeremo una pagina AddToCart.aspx che non solo chiama questo metodo, ma successivamente visualizza il carrello attuale dopo che l'elemento è stato aggiunto.
Fare clic con il tasto destro sul nome della soluzione in Esplora soluzioni e aggiungere una nuova pagina denominata AddToCart.aspx come è stato fatto in precedenza.
Anche se è possibile usare questa pagina per visualizzare risultati provvisori come problemi di scorte basse e così via, nell'implementazione, la pagina non eseguirà effettivamente il rendering, ma chiamerà invece la logica "Aggiungi" e il reindirizzamento.
A tale scopo, aggiungeremo il codice seguente all'evento 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");
}
}
}
Si noti che si sta recuperando il prodotto da aggiungere al carrello acquisti da un parametro QueryString e chiamando il metodo AddItem della classe.
Supponendo che non vengano rilevati errori, il controllo viene passato alla pagina SHoppingCart.aspx che verrà implementata completamente successivamente. Se si verifica un errore, viene generata un'eccezione.
Attualmente non è ancora stato implementato un gestore di errori globale, quindi questa eccezione non verrà gestita dall'applicazione, ma questa operazione verrà rimediata a breve.
Si noti anche l'uso dell'istruzione Debug.Fail() (disponibile tramite using System.Diagnostics;)
L'applicazione è in esecuzione all'interno del debugger, questo metodo visualizzerà una finestra di dialogo dettagliata con informazioni sullo stato delle applicazioni insieme al messaggio di errore specificato.
Quando si esegue nell'ambiente di produzione, l'istruzione Debug.Fail() viene ignorata.
Si noterà nel codice sopra una chiamata a un metodo nella classe del carrello acquisti chiamato "GetShoppingCartId".
Aggiungere il codice per implementare il metodo come indicato di seguito.
Si noti che sono stati aggiunti anche pulsanti di aggiornamento e checkout e un'etichetta in cui è possibile visualizzare il carrello "totale".
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();
}
È ora possibile aggiungere articoli al carrello, ma non è stata implementata la logica per visualizzare il carrello dopo l'aggiunta di un prodotto.
Quindi, nella pagina MyShoppingCart.aspx aggiungeremo un controllo EntityDataSource e un controllo GridVire come indicato di seguito.
<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>
Chiamare il formulario nella finestra di progettazione in modo che sia possibile fare doppio click sul pulsante Aggiorna carrello e generare il gestore dell'evento click specificato nella dichiarazione del markup.
I dettagli verranno implementati in un secondo momento, ma questa operazione consentirà di compilare ed eseguire l'applicazione senza errori.
Quando si esegue l'applicazione e si aggiunge un elemento al carrello acquisti verrà visualizzato.
Si noti che ci siamo discostati dalla visualizzazione della griglia "predefinita" implementando tre colonne personalizzate.
Il primo è un campo modificabile "Bound" per 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 seguente è una colonna "calcolata" che visualizza il totale della voce (il costo dell'articolo moltiplicato per la quantità da ordinare):
<asp:TemplateField>
<HeaderTemplate>Item Total</HeaderTemplate>
<ItemTemplate>
<%# (Convert.ToDouble(Eval("Quantity")) *
Convert.ToDouble(Eval("UnitCost")))%>
</ItemTemplate>
</asp:TemplateField>
Infine, abbiamo una colonna personalizzata che contiene un controllo CheckBox che l'utente userà per indicare che l'elemento deve essere rimosso dal grafico acquisti.
<asp:TemplateField>
<HeaderTemplate>Remove Item</HeaderTemplate>
<ItemTemplate>
<center>
<asp:CheckBox id="Remove" runat="server" />
</center>
</ItemTemplate>
</asp:TemplateField>
Come si può notare, la riga Order Total è vuota, quindi aggiungiamo una logica per calcolare il totale dell'ordine.
Verrà prima implementato un metodo "GetTotal" per la classe MyShoppingCart.
Nel file MyShoppingCart.cs aggiungere il codice seguente.
//--------------------------------------------------------------------------------------+
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);
}
}
Nel gestore eventi Page_Load sarà quindi possibile chiamare il metodo GetTotal. Allo stesso tempo aggiungeremo un test per verificare se il carrello acquisti è vuoto e regolare la visualizzazione di conseguenza, se lo è.
Ora, se il carrello acquisti è vuoto, si ottiene questo:
E in caso contrario, vediamo il nostro totale.
Tuttavia, questa pagina non è ancora stata completata.
Sarà necessaria logica aggiuntiva per ricalcolare il carrello degli acquisti rimuovendo gli articoli contrassegnati per la rimozione e determinando nuovi valori di quantità in quanto alcuni potrebbero essere stati modificati nella griglia dall'utente.
Consente di aggiungere un metodo "RemoveItem" alla classe del carrello acquisti in MyShoppingCart.cs per gestire il caso quando un utente contrassegna un elemento per la rimozione.
//------------------------------------------------------------------------------------+
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);
}
}
}
A questo punto, aggiungiamo un metodo per gestire la circostanza quando un utente modifica semplicemente la qualità in modo che venga ordinata in 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);
}
}
}
Con le funzionalità di base rimuovi e aggiornamento è possibile implementare la logica che aggiorna effettivamente il carrello acquisti nel database. (In 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);
}
}
}
Si noti che questo metodo prevede due parametri. Uno è l'ID carrello acquisti e l'altro è una matrice di oggetti di tipo definito dall'utente.
Per ridurre al minimo la dipendenza della logica sulle specifiche dell'interfaccia utente, è stata definita una struttura di dati che è possibile usare per passare gli elementi del carrello acquisti al codice senza che il metodo debba accedere direttamente al controllo GridView.
public struct ShoppingCartUpdates
{
public int ProductId;
public int PurchaseQantity;
public bool RemoveItem;
}
Nel file MyShoppingCart.aspx.cs è possibile usare questa struttura nel gestore eventi Update Button Click come indicato di seguito. Si noti che oltre ad aggiornare il carrello aggiorneremo anche il totale del carrello.
//--------------------------------------------------------------------------------------+
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));
}
Si noti con particolare interesse questa riga di codice:
rowValues = GetValues(MyList.Rows[i]);
GetValues() è una funzione helper speciale che verrà implementata in MyShoppingCart.aspx.cs come indicato di seguito.
//--------------------------------------------------------------------------------------+
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;
}
In questo modo è possibile accedere ai valori degli elementi associati nel controllo GridView. Poiché il controllo CheckBox "Rimuovi elemento" non è associato, sarà possibile accedervi tramite il metodo FindControl().
In questa fase dello sviluppo del progetto ci stiamo preparando per implementare il processo di pagamento.
Prima di eseguire questa operazione, usare Visual Studio per generare il database di appartenenza e aggiungere un utente al repository di appartenenza.