5부: 비즈니스 논리

작성자 : Joe Stagner

Tailspin Spyworks는 .NET 플랫폼에 대해 강력하고 확장 가능한 애플리케이션을 만드는 것이 얼마나 간단한지 보여 줍니다. ASP.NET 4의 새로운 기능을 사용하여 쇼핑, 체크 아웃 및 관리를 포함한 온라인 스토어를 구축하는 방법을 보여줍니다.

이 자습서 시리즈에서는 Tailspin Spyworks 샘플 애플리케이션을 빌드하기 위해 수행되는 모든 단계를 자세히 설명합니다. 5부에서는 일부 비즈니스 논리를 추가합니다.

일부 비즈니스 논리 추가

우리는 누군가가 우리의 웹 사이트를 방문 할 때마다 우리의 쇼핑 경험을 사용할 수 있기를 바랍니다. 방문자는 등록하거나 로그인하지 않은 경우에도 쇼핑 카트에 항목을 찾아보고 추가할 수 있습니다. 검사 준비가 되면 인증 옵션이 제공되고 아직 구성원이 아닌 경우 계정을 만들 수 있습니다.

즉, 쇼핑 카트를 익명 상태에서 "등록된 사용자" 상태로 변환하는 논리를 구현해야 합니다.

"Classes"라는 디렉터리를 만든 다음 폴더에 Right-Click MyShoppingCart.cs라는 새 "클래스" 파일을 만들어 보겠습니다.

My Shopping Cart dot C S라는 새 클래스 파일을 보여 주는 스크린샷

클래스 폴더의 내용을 보여 주는 스크린샷

앞에서 설명한 대로 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)
        {

        }
    }
}

"부분" 키워드(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 페이지로 전달됩니다. 오류가 발생하면 예외를 throw합니다.

현재는 아직 전역 오류 처리기를 구현하지 않았으므로 애플리케이션에서 이 예외를 처리하지 않지만 곧 이 문제를 해결할 것입니다.

Debug.Fail() 문도 사용합니다(를 통해 사용 가능). using System.Diagnostics;)

애플리케이션이 디버거 내에서 실행되고 있나요? 이 메서드는 지정한 오류 메시지와 함께 애플리케이션 상태에 대한 정보가 포함된 자세한 대화 상자를 표시합니다.

프로덕션 환경에서 실행하는 경우 Debug.Fail() 문이 무시됩니다.

위의 코드에서는 쇼핑 카트 클래스 이름 "GetShoppingCartId"에서 메서드를 호출합니다.

다음과 같이 메서드를 구현하는 코드를 추가합니다.

업데이트 및 체크 아웃 단추와 카트 "합계"를 표시할 수 있는 레이블도 추가되었습니다.

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>

카트 업데이트 단추를 두 번 클릭하고 태그의 선언에 지정된 클릭 이벤트 처리기를 생성할 수 있도록 디자이너에서 양식을 호출합니다.

나중에 세부 정보를 구현하지만 이렇게 하면 오류 없이 애플리케이션을 빌드하고 실행할 수 있습니다.

애플리케이션을 실행하고 쇼핑 카트에 항목을 추가하면 이 항목이 표시됩니다.

업데이트된 쇼핑 카트를 보여 주는 스크린샷

세 개의 사용자 지정 열을 구현하여 "기본" 그리드 표시에서 벗어나게 했습니다.

첫 번째는 수량에 대한 편집 가능한 "바인딩된" 필드입니다.

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

이 메서드에는 두 개의 매개 변수가 예상됩니다. 하나는 쇼핑 카트 ID이고 다른 하나는 사용자 정의 형식의 개체 배열입니다.

따라서 사용자 인터페이스 세부 사항에 대한 논리의 종속성을 최소화하기 위해 GridView 컨트롤에 직접 액세스할 필요 없이 쇼핑 카트 항목을 코드에 전달하는 데 사용할 수 있는 데이터 구조를 정의했습니다.

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

MyShoppingCart.aspx.cs 파일에서 다음과 같이 업데이트 단추 클릭 이벤트 처리기에서 이 구조를 사용할 수 있습니다. 카트를 업데이트하는 것 외에도 카트 총계도 업데이트합니다.

//--------------------------------------------------------------------------------------+
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 컨트롤에서 바인딩된 요소의 값에 액세스할 수 있는 클린 방법을 제공합니다. "항목 제거" CheckBox 컨트롤이 바인딩되지 않으므로 FindControl() 메서드를 통해 액세스합니다.

프로젝트 개발의 이 단계에서는 체크 아웃 프로세스를 구현할 준비를 하고 있습니다.

이렇게 하기 전에 Visual Studio를 사용하여 멤버 자격 데이터베이스를 생성하고 멤버 자격 리포지토리에 사용자를 추가해 보겠습니다.