第 5 部:ビジネス ロジック

作成者: Joe Stagner

Tailspin Spyworks は、.NET プラットフォーム用の強力でスケーラブルなアプリケーションを作成することが非常にシンプルであることを示しています。 ASP.NET 4 の優れた新機能を使用して、ショッピング、チェックアウト、管理などのオンライン ストアを構築する方法を示します。

このチュートリアル シリーズでは、Tailspin Spyworks サンプル アプリケーションをビルドするために実行されるすべての手順について詳しく説明します。 パート 5 では、いくつかのビジネス ロジックが追加されます。

ビジネス ロジックの追加

私たちは、誰かが私たちのウェブサイトを訪問するたびに私たちのショッピング体験を利用できるようにしたいと考えています。 訪問者は、登録またはログインしていない場合でも、ショッピング カートにアイテムを参照して追加できます。 チェックする準備ができたら、認証するオプションが与えられ、まだメンバーでない場合はアカウントを作成できます。

つまり、ショッピング カートを匿名状態から "登録済みユーザー" 状態に変換するロジックを実装する必要があります。

"Classes" という名前のディレクトリを作成し、フォルダーにRight-Clickし、MyShoppingCart.cs という名前の新しい "Class" ファイルを作成しましょう

[マイ ショッピング カート] ドット C S という名前の新しいクラス ファイルを示すスクリーンショット。

Classes フォルダーの内容を示すスクリーンショット。

前述のように、MyShoppingCart.aspx ページを実装するクラスを拡張し、 を使用してこれを行います。NET の強力な "部分クラス" コンストラクト。

MyShoppingCart.aspx.cf ファイルの生成された呼び出しは次のようになります。

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

        }
    }
}

"partial" キーワード (keyword)の使用に注意してください。

生成したクラス ファイルは次のようになります。

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

namespace TailspinSpyworks.Classes
{
    public class MyShoppingCart
    {
    }
}

このファイルに部分的なキーワード (keyword)を追加して、実装をマージします。

新しいクラス ファイルは次のようになります。

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

クラスに追加する最初のメソッドは、"AddItem" メソッドです。 これは、ユーザーが [製品リスト] ページと [製品の詳細] ページの [アートに追加] リンクをクリックしたときに最終的に呼び出されるメソッドです。

ページの上部にある using ステートメントに以下を追加します。

using TailspinSpyworks.Data_Access;

このメソッドを 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);
      }
   }
}

LINQ to Entitiesを使用して、項目が既にカートに入っているかどうかを確認しています。 その場合は、品目の注文数量を更新します。それ以外の場合は、選択した品目の新しいエントリを作成します

このメソッドを呼び出すために、このメソッドをクラス化するだけでなく、アイテムが追加された後に現在のショッピング a=cart を表示する AddToCart.aspx ページを実装します。

ソリューション エクスプローラーでソリューション名をRight-Clickし、前に行った AddToCart.aspx という名前の新しいページを追加します。

このページを使用して、低在庫の問題などの中間結果を実装で表示できますが、ページは実際にはレンダリングされず、"追加" ロジックとリダイレクトを呼び出します。

これを実現するには、次のコードを 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");
        }
    }
}

QueryString パラメーターからショッピング カートに追加する製品を取得し、クラスの AddItem メソッドを呼び出すことに注意してください。

エラーが発生しなかったと仮定すると、次に完全に実装する SHoppingCart.aspx ページに制御が渡されます。 エラーが発生した場合は、例外をスローします。

現在、グローバル エラー ハンドラーはまだ実装されていないため、この例外はアプリケーションによって処理されませんが、すぐに解決します。

ステートメント Debug.Fail() の使用にも注意してください (を介して使用できます) using System.Diagnostics;)

アプリケーションがデバッガー内で実行されている場合、このメソッドでは、アプリケーションの状態に関する情報と、指定したエラー メッセージを含む詳細なダイアログが表示されます。

運用環境で実行している場合、Debug.Fail() ステートメントは無視されます。

上のコードでは、ショッピング カート クラス名 "GetShoppingCartId" のメソッドの呼び出しに注意してください。

メソッドを実装するコードを次のように追加します。

また、更新ボタンとチェックアウト ボタンと、カート "total" を表示できるラベルも追加されていることに注意してください。

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

ショッピング カートに項目を追加できるようになりましたが、製品の追加後にカートを表示するロジックは実装されていません。

そのため、MyShoppingCart.aspx ページでは、次のように EntityDataSource コントロールと GridVire コントロールを追加します。

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

デザイナーでフォームを呼び出して、[カートの更新] ボタンをダブルクリックし、マークアップの宣言で指定されているクリック イベント ハンドラーを生成できるようにします。

詳細は後で実装しますが、これを行うと、エラーなしでアプリケーションをビルドして実行できます。

アプリケーションを実行し、ショッピング カートに項目を追加すると、これが表示されます。

更新されたショッピング カートを示すスクリーンショット。

3 つのカスタム列を実装することで、"既定の" グリッド表示から逸脱したことに注意してください。

1 つ目は、Quantity の編集可能な "連結" フィールドです。

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

最後に、ユーザーがショッピング チャートからアイテムを削除する必要があることを示すために使用する CheckBox コントロールを含むカスタム列があります。

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

更新された数量とアイテムの削除を示すスクリーンショット。

ご覧のように、[受注合計] 行は空なので、注文合計を計算するためのロジックをいくつか追加しましょう。

まず、MyShoppingCart クラスに "GetTotal" メソッドを実装します。

MyShoppingCart.cs ファイルに次のコードを追加します。

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

次に、Page_Load イベント ハンドラーで GetTotal メソッドを呼び出すことができます。 同時に、ショッピング カートが空かどうかを確認するテストを追加し、表示されている場合はそれに応じて調整します。

買い物かごが空の場合は、次のようになります。

空のショッピング カートを示すスクリーンショット。

そうでない場合は、合計が表示されます。

ショッピング カート内のアイテムの合計金額を示すスクリーンショット。

ただし、このページはまだ完了していません。

削除対象としてマークされた項目を削除し、ユーザーによってグリッドで変更された可能性があるため、新しい数量値を決定することで、ショッピング カートを再計算するための追加のロジックが必要になります。

MyShoppingCart.cs のショッピング カート クラスに "RemoveItem" メソッドを追加して、ユーザーがアイテムに削除のマークを付けた場合のケースを処理できるようにします。

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

次に、ユーザーが 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);
        }
    }
}

基本的な削除機能と更新機能を使用すると、実際にデータベース内のショッピング カートを更新するロジックを実装できます。 (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);
        }            
    }           
}

このメソッドには 2 つのパラメーターが必要であることに注意してください。 1 つはショッピング カート ID で、もう 1 つはユーザー定義型のオブジェクトの配列です。

ユーザー インターフェイスの詳細に対するロジックの依存関係を最小限に抑えるために、メソッドが GridView コントロールに直接アクセスする必要なく、ショッピング カート項目をコードに渡すために使用できるデータ構造を定義しました。

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

MyShoppingCart.aspx.cs ファイルでは、次のように Update Button Click イベント ハンドラーでこの構造を使用できます。 カートの更新に加えて、カートの合計も更新されることに注意してください。

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

特に関心のあるコード行に注意してください。

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

GetValues() は、次のように MyShoppingCart.aspx.cs に実装する特別なヘルパー関数です。

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

これにより、GridView コントロール内のバインドされた要素の値にアクセスするためのクリーンな方法が提供されます。 "Remove Item" CheckBox Control はバインドされていないため、FindControl() メソッドを使用してアクセスします。

プロジェクトの開発のこの段階では、チェックアウト プロセスを実装する準備が整っています。

その前に、Visual Studio を使用してメンバーシップ データベースを生成し、メンバーシップ リポジトリにユーザーを追加しましょう。