Parte 5: Logica di business

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 di business

Vogliamo che la nostra esperienza di shopping sia disponibile ogni volta che qualcuno visita il nostro sito Web. I visitatori potranno esplorare e aggiungere articoli al carrello acquisti anche se non sono registrati o connessi. Quando sono pronti per l'estrazione, verranno assegnati l'opzione per 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" e quindi Right-Click nella cartella e creare un nuovo file "Class" denominato MyShoppingCart.cs

Screenshot che mostra il nuovo file di classe denominato My Shopping Cart dot C S.

Screenshot che mostra il contenuto della cartella Classi.

Come accennato in precedenza, verrà estesa la classe che implementa la pagina MyShoppingCart.aspx e verrà eseguita questa operazione usando . Costrutto "Classe parziale" potente di NET.

La chiamata generata per il file di MyShoppingCart.aspx.cf ha un aspetto simile al 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 "parziale".

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

Verranno unite le implementazioni aggiungendo anche la parola chiave parziale a questo file.

Il nuovo file di classe è ora 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 in definitiva quando l'utente fa clic sui collegamenti "Aggiungi a arte" nelle pagine Product List e Product Details.

Aggiungere quanto segue alle istruzioni 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);
      }
   }
}

Stiamo usando LINQ to Entities per verificare se l'elemento è già nel carrello. In tal caso, aggiorniamo la quantità di ordine dell'elemento, altrimenti creiamo una nuova voce per l'elemento selezionato

Per chiamare questo metodo, verrà implementata una pagina AddToCart.aspx che non solo classe questo metodo, ma quindi visualizzerà l'oggetto shopping corrente a=carrello dopo l'aggiunta dell'elemento.

Right-Click sul nome della soluzione in Esplora soluzioni e aggiungere e nuova pagina denominata AddToCart.aspx come è stato fatto in precedenza.

Anche se è possibile usare questa pagina per visualizzare risultati provvisori come problemi di stock bassi e così via, nell'implementazione, la pagina non verrà effettivamente eseguita, ma invece chiamare la logica "Aggiungi" e il reindirizzamento.

A questo scopo, si aggiungerà 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 per 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à completamente implementata successivamente. Se deve verificarsi un errore, viene generata un'eccezione.

Attualmente non è ancora stato implementato un gestore di errori globale in modo che questa eccezione non venga gestita dall'applicazione, ma questo verrà rimediato 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.

Durante l'esecuzione nell'istruzione Debug.Fail() viene ignorata.

Si noti nel codice precedente una chiamata a un metodo nei nomi delle classi del carrello acquisti "GetShoppingCartId".

Aggiungere il codice per implementare il metodo come indicato di seguito.

Si noti che sono stati aggiunti anche pulsanti di aggiornamento e pagamento 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 si aggiungerà 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 modulo nella finestra di progettazione in modo che sia possibile fare doppio clic sul pulsante Aggiorna carrello e generare il gestore eventi click specificato nella dichiarazione nel markup.

Verranno implementati i dettagli più avanti, 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, questo verrà visualizzato.

Screenshot che mostra il carrello acquisti aggiornato.

Si noti che la griglia "predefinita" è stata deviata 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 successiva è una colonna "calcolata" che visualizza il totale dell'elemento della riga (il costo dell'elemento deve essere ordinato):

<asp:TemplateField> 
      <HeaderTemplate>Item Total</HeaderTemplate>
      <ItemTemplate>
        <%# (Convert.ToDouble(Eval("Quantity")) *  
             Convert.ToDouble(Eval("UnitCost")))%>
      </ItemTemplate>
    </asp:TemplateField>

Infine, è disponibile una colonna personalizzata che contiene un controllo CheckBox che l'utente userà per indicare che l'elemento deve essere rimosso dal grafico shopping.

<asp:TemplateField> 
    <HeaderTemplate>Remove Item</HeaderTemplate>
      <ItemTemplate>
        <center>
          <asp:CheckBox id="Remove" runat="server" />
        </center>
      </ItemTemplate>
    </asp:TemplateField>

Screenshot che mostra la quantità aggiornata e rimuovi elementi.

Come si può notare, la riga Order Total è vuota, quindi aggiungere una logica per calcolare il totale dell'ordine.

Verrà innanzitutto implementato un metodo "GetTotal" alla 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 si aggiungerà un test per verificare se il carrello acquisti è vuoto e regolare la visualizzazione di conseguenza se è.

Ora se il carrello acquisti è vuoto si ottiene questo:

Screenshot che mostra il carrello acquisti vuoto.

E, in caso contrario, vediamo il nostro totale.

Screenshot che mostra l'importo totale per gli articoli nel carrello acquisti.

Tuttavia, questa pagina non è ancora completa.

Sarà necessaria una logica aggiuntiva per ricalcolare il carrello degli acquisti rimuovendo gli elementi 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);
        }
    }
}

Si supponga ora di inviare un annuncio a un metodo per gestire le circostanze quando un utente modifica semplicemente la qualità da ordinare 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 Aggiorna è 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 del 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 dell'evento Update Button Click Nel seguente modo. Si noti che oltre all'aggiornamento del 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 "Remove Item" non è associato, verrà eseguito l'accesso tramite il metodo FindControl().

In questa fase dello sviluppo del progetto si sta 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.