Share via


Teil 5: Geschäftslogik

von Joe Stagner

Tailspin Spyworks zeigt, wie einfach es ist, leistungsstarke, skalierbare Anwendungen für die .NET-Plattform zu erstellen. Es zeigt, wie Sie die großartigen neuen Features in ASP.NET 4 verwenden, um einen Onlineshop zu erstellen, einschließlich Einkaufen, Auschecken und Verwaltung.

In dieser Tutorialreihe werden alle Schritte zum Erstellen der Tailspin Spyworks-Beispielanwendung beschrieben. Teil 5 fügt geschäftslogik hinzu.

Hinzufügen von Geschäftslogik

Wir möchten, dass unser Einkaufserlebnis verfügbar ist, wenn jemand unsere Website besucht. Besucher können artikel durchsuchen und dem Warenkorb hinzufügen, auch wenn sie nicht registriert oder angemeldet sind. Wenn sie zum Auschecken bereit sind, erhalten sie die Möglichkeit, sich zu authentifizieren, und wenn sie noch keine Mitglieder sind, können sie ein Konto erstellen.

Dies bedeutet, dass wir die Logik implementieren müssen, um den Warenkorb von einem anonymen Zustand in den Status "Registrierter Benutzer" zu konvertieren.

Wir erstellen ein Verzeichnis namens "Classes", Right-Click dann im Ordner und erstellen eine neue "Class"-Datei mit dem Namen MyShoppingCart.cs.

Screenshot der neuen Klassendatei mit dem Namen

Screenshot: Inhalt des Ordners

Wie bereits erwähnt erweitern wir die -Klasse, die die Seite MyShoppingCart.aspx implementiert, und wir führen dies mithilfe von aus. Das leistungsstarke "Partial Class"-Konstrukt von NET.

Der generierte Aufruf für unsere MyShoppingCart.aspx.cf-Datei sieht wie folgt aus.

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

        }
    }
}

Beachten Sie die Verwendung des "partiellen" Schlüsselwort (keyword).

Die soeben generierte Klassendatei sieht wie folgt aus.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace TailspinSpyworks.Classes
{
    public class MyShoppingCart
    {
    }
}

Wir werden unsere Implementierungen zusammenführen, indem wir der Datei auch die partielle Schlüsselwort (keyword) hinzufügen.

Unsere neue Klassendatei sieht jetzt wie folgt aus.

namespace TailspinSpyworks.Classes
{
    public partial class MyShoppingCart
    {
    }
}

Die erste Methode, die wir unserer Klasse hinzufügen, ist die "AddItem"-Methode. Dies ist die Methode, die letztendlich aufgerufen wird, wenn der Benutzer auf die Links "Zu Kunst hinzufügen" auf den Seiten "Produktliste" und "Produktdetails" klickt.

Fügen Sie folgendes an die using-Anweisungen oben auf der Seite an.

using TailspinSpyworks.Data_Access;

Fügen Sie diese Methode der MyShoppingCart-Klasse hinzu.

//------------------------------------------------------------------------------------+
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);
      }
   }
}

Wir verwenden LINQ to Entities, um zu sehen, ob sich der Artikel bereits im Warenkorb befindet. Wenn ja, aktualisieren wir die Bestellmenge des Artikels, andernfalls erstellen wir einen neuen Eintrag für den ausgewählten Artikel.

Um diese Methode aufzurufen, implementieren wir eine AddToCart.aspx-Seite, die nicht nur diese Methode einordnet, sondern dann den aktuellen Shopping a=cart anzeigt, nachdem das Element hinzugefügt wurde.

Right-Click im Projektmappen-Explorer auf den Projektmappennamen, und fügen Sie wie zuvor die neue Seite AddToCart.aspx hinzu.

Während wir diese Seite verwenden könnten, um Zwischenergebnisse wie niedrige Lagerbestände usw. anzuzeigen, wird die Seite in unserer Implementierung nicht gerendert, sondern ruft stattdessen die Logik "Hinzufügen" und die Umleitung auf.

Um dies zu erreichen, fügen wir den folgenden Code zum Page_Load-Ereignis hinzu.

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

Beachten Sie, dass wir das Produkt abrufen, das dem Warenkorb aus einem QueryString-Parameter hinzugefügt werden soll, und die AddItem-Methode unserer Klasse aufrufen.

Wenn keine Fehler auftreten, wird das Steuerelement an die Seite SHoppingCart.aspx übergeben, die als Nächstes vollständig implementiert wird. Sollte ein Fehler auftreten, lösen wir eine Ausnahme aus.

Derzeit haben wir noch keinen globalen Fehlerhandler implementiert, sodass diese Ausnahme von unserer Anwendung nicht behandelt wird, aber wir werden dies in Kürze beheben.

Beachten Sie auch die Verwendung der Anweisung Debug.Fail() (verfügbar über using System.Diagnostics;)

Wird die Anwendung im Debugger ausgeführt, zeigt diese Methode ein ausführliches Dialogfeld mit Informationen zum Anwendungsstatus zusammen mit der von uns angegebenen Fehlermeldung an.

Bei der Ausführung in der Produktion wird die Debug.Fail()-Anweisung ignoriert.

Sie werden im obigen Code einen Aufruf einer Methode in unserem Warenkorb Klassennamen "GetShoppingCartId" notieren.

Fügen Sie den Code hinzu, um die -Methode wie folgt zu implementieren.

Beachten Sie, dass wir auch Schaltflächen zum Aktualisieren und Auschecken sowie eine Bezeichnung hinzugefügt haben, auf der wir den Warenkorb "gesamt" anzeigen können.

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();
}

Wir können jetzt Artikel zu unserem Warenkorb hinzufügen, aber wir haben nicht die Logik implementiert, um den Warenkorb anzuzeigen, nachdem ein Produkt hinzugefügt wurde.

Daher fügen wir auf der Seite MyShoppingCart.aspx wie folgt ein EntityDataSource-Steuerelement und ein GridVire-Steuerelement hinzu.

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

Rufen Sie das Formular im Designer auf, damit Sie auf die Schaltfläche Warenkorb aktualisieren doppelklicken und den Click-Ereignishandler generieren können, der in der Deklaration im Markup angegeben ist.

Wir implementieren die Details später, aber dadurch können wir unsere Anwendung ohne Fehler erstellen und ausführen.

Wenn Sie die Anwendung ausführen und dem Warenkorb ein Element hinzufügen, wird dies angezeigt.

Screenshot, der den aktualisierten Warenkorb zeigt.

Beachten Sie, dass wir von der "Standard"-Rasteranzeige abgewichen sind, indem wir drei benutzerdefinierte Spalten implementieren.

Die erste ist ein bearbeitbares Feld "Bound" für die Menge:

<asp:TemplateField> 
      <HeaderTemplate>Quantity</HeaderTemplate>
      <ItemTemplate>
         <asp:TextBox ID="PurchaseQuantity" Width="40" runat="server" 
                      Text='<%# Bind("Quantity") %>'></asp:TextBox> 
      </ItemTemplate>
    </asp:TemplateField>

Die nächste ist eine "berechnete" Spalte, in der die Summe der Position angezeigt wird (der Artikel kostet die zu bestellende Menge):

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

Schließlich verfügen wir über eine benutzerdefinierte Spalte, die ein CheckBox-Steuerelement enthält, das der Benutzer verwendet, um anzugeben, dass das Element aus dem Einkaufsdiagramm entfernt werden soll.

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

Screenshot: Aktualisierte Menge und Elemente entfernen

Wie Sie sehen können, ist die Zeile "Order Total" leer, daher fügen wir eine Logik hinzu, um die Bestellsumme zu berechnen.

Wir implementieren zunächst eine "GetTotal"-Methode für unsere MyShoppingCart-Klasse.

Fügen Sie in der Datei MyShoppingCart.cs den folgenden Code hinzu.

//--------------------------------------------------------------------------------------+
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);
     }
}

Anschließend können wir im Page_Load-Ereignishandler die GetTotal-Methode aufrufen. Gleichzeitig fügen wir einen Test hinzu, um zu sehen, ob der Warenkorb leer ist, und passen die Anzeige entsprechend an, wenn dies der Fall ist.

Wenn der Warenkorb leer ist, erhalten wir folgendes:

Screenshot, der den leeren Warenkorb zeigt.

Und wenn nicht, sehen wir unsere Summe.

Screenshot, der den Gesamtbetrag für die Artikel im Warenkorb zeigt.

Diese Seite ist jedoch noch nicht vollständig.

Wir benötigen zusätzliche Logik, um den Einkaufswagen neu zu berechnen, indem elemente entfernt werden, die zum Entfernen markiert sind, und indem wir neue Mengenwerte ermitteln, da einige möglicherweise vom Benutzer im Raster geändert wurden.

Fügen wir der Warenkorbklasse in MyShoppingCart.cs eine "RemoveItem"-Methode hinzu, um den Fall zu behandeln, wenn ein Benutzer ein Element zum Entfernen markiert.

//------------------------------------------------------------------------------------+
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);
        }
    }
}

Nun wird eine Methode zur Behandlung des Umstands angezeigt, in dem ein Benutzer einfach die Qualität ändert, um in GridView bestellt zu werden.

//--------------------------------------------------------------------------------------+
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);
        }
    }
}

Mit den grundlegenden Remove- und Update-Features können wir die Logik implementieren, mit der der Warenkorb in der Datenbank tatsächlich aktualisiert wird. (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);
        }            
    }           
}

Sie werden feststellen, dass diese Methode zwei Parameter erwartet. Eine ist die Warenkorb-ID und die andere ist ein Array von Objekten des benutzerdefinierten Typs.

Um die Abhängigkeit unserer Logik von Benutzeroberflächen-Besonderheiten zu minimieren, haben wir eine Datenstruktur definiert, die wir verwenden können, um die Warenkorbelemente an unseren Code zu übergeben, ohne dass unsere Methode direkt auf das GridView-Steuerelement zugreifen muss.

public struct ShoppingCartUpdates
{
    public int ProductId;
    public int PurchaseQantity;
    public bool RemoveItem;
}

In der Datei MyShoppingCart.aspx.cs können wir diese Struktur wie folgt in unserem Update Button Click Event-Handler verwenden. Beachten Sie, dass zusätzlich zum Aktualisieren des Warenkorbs auch die Warenkorbsumme aktualisiert wird.

//--------------------------------------------------------------------------------------+
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));
}

Beachten Sie mit besonderem Interesse diese Codezeile:

rowValues = GetValues(MyList.Rows[i]);

GetValues() ist eine spezielle Hilfsfunktion, die wie folgt in MyShoppingCart.aspx.cs implementiert wird.

//--------------------------------------------------------------------------------------+
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;
}

Dies bietet eine sauber Möglichkeit, auf die Werte der gebundenen Elemente in unserem GridView-Steuerelement zuzugreifen. Da das CheckBox-Steuerelement "Element entfernen" nicht gebunden ist, greifen wir über die FindControl()-Methode darauf zu.

In dieser Phase der Projektentwicklung bereiten wir uns auf die Implementierung des Checkout-Prozesses vor.

Bevor wir dies tun, verwenden wir Visual Studio, um die Mitgliedschaftsdatenbank zu generieren und einen Benutzer zum Mitgliedschaftsrepository hinzuzufügen.