다음을 통해 공유


7부: 기능 추가

작성자: Joe Stagner

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

이 자습서 시리즈에서는 Tailspin Spyworks 샘플 애플리케이션을 빌드하기 위해 수행된 모든 단계를 자세히 설명합니다. 7부에서는 계정 검토, 제품 리뷰, "인기 항목" 및 "구매한" 사용자 컨트롤과 같은 추가 기능을 추가합니다.

기능 추가

사용자가 카탈로그를 찾아보고, 쇼핑 카트에 항목을 배치하고, 체크 아웃 프로세스를 완료할 수 있지만, 사이트를 개선하기 위해 포함할 지원 기능이 많이 있습니다.

  1. 계정 검토(주문 나열 및 세부 정보 보기).
  2. 첫 페이지에 일부 컨텍스트별 콘텐츠를 추가합니다.
  3. 사용자가 카탈로그에서 제품을 검토할 수 있도록 기능을 추가합니다.
  4. 사용자 컨트롤을 만들어 인기 있는 항목을 표시하고 해당 컨트롤을 첫 페이지에 배치합니다.
  5. "또한 구매한" 사용자 컨트롤을 만들고 제품 세부 정보 페이지에 추가합니다.
  6. 연락처 페이지를 추가합니다.
  7. 정보 페이지를 추가합니다.
  8. 전역 오류

계정 검토

"계정" 폴더에서 OrderList.aspx라는 두 개의 .aspx 페이지와 OrderDetails.aspx라는 다른 페이지를 만듭니다.

OrderList.aspx는 이전과 마찬가지로 GridView 및 EntityDataSource 컨트롤을 활용합니다.

<div class="ContentHead">Order History</div><br />

<asp:GridView ID="GridView_OrderList" runat="server" AllowPaging="True" 
              ForeColor="#333333" GridLines="None" CellPadding="4" Width="100%" 
              AutoGenerateColumns="False" DataKeyNames="OrderID" 
              DataSourceID="EDS_Orders" AllowSorting="True" ViewStateMode="Disabled" >
  <AlternatingRowStyle BackColor="White" />
  <Columns>
    <asp:BoundField DataField="OrderID" HeaderText="OrderID" ReadOnly="True" 
                    SortExpression="OrderID" />
    <asp:BoundField DataField="CustomerName" HeaderText="Customer" 
                    SortExpression="CustomerName" />
    <asp:BoundField DataField="OrderDate" HeaderText="Order Date" 
                    SortExpression="OrderDate" />
    <asp:BoundField DataField="ShipDate" HeaderText="Ship Date" 
                    SortExpression="ShipDate" />
    <asp:HyperLinkField HeaderText="Show Details" Text="Show Details" 
                 DataNavigateUrlFields="OrderID" 
                 DataNavigateUrlFormatString="~/Account/OrderDetails.aspx?OrderID={0}" />
  </Columns>
  <FooterStyle BackColor="#990000" Font-Bold="True" ForeColor="White" />
  <HeaderStyle BackColor="#990000" Font-Bold="True" ForeColor="White" />
  <PagerStyle BackColor="#FFCC66" ForeColor="#333333" HorizontalAlign="Center" />
  <RowStyle BackColor="#FFFBD6" ForeColor="#333333" />
  <SelectedRowStyle BackColor="#FFCC66" Font-Bold="True" ForeColor="Navy" />
  <SortedAscendingCellStyle BackColor="#FDF5AC" />
  <SortedAscendingHeaderStyle BackColor="#4D0000" />
  <SortedDescendingCellStyle BackColor="#FCF6C0" />
  <SortedDescendingHeaderStyle BackColor="#820000" />
  <SortedAscendingCellStyle BackColor="#FDF5AC"></SortedAscendingCellStyle>
  <SortedAscendingHeaderStyle BackColor="#4D0000"></SortedAscendingHeaderStyle>
  <SortedDescendingCellStyle BackColor="#FCF6C0"></SortedDescendingCellStyle>
  <SortedDescendingHeaderStyle BackColor="#820000"></SortedDescendingHeaderStyle>
</asp:GridView>

<asp:EntityDataSource ID="EDS_Orders" runat="server" EnableFlattening="False" 
                      AutoGenerateWhereClause="True" 
                      Where="" 
                      OrderBy="it.OrderDate DESC"
                      ConnectionString="name=CommerceEntities"  
                      DefaultContainerName="CommerceEntities" 
                      EntitySetName="Orders" >
   <WhereParameters>
      <asp:SessionParameter Name="CustomerName" SessionField="UserName" />
   </WhereParameters>
</asp:EntityDataSource>

EntityDataSource는 사용자가 로그인할 때 세션 변수에 설정한 UserName(WhereParameter 참조)에서 필터링된 Orders 테이블에서 레코드를 선택합니다.

GridView의 HyperlinkField에서도 이러한 매개 변수를 확인합니다.

DataNavigateUrlFields="OrderID" DataNavigateUrlFormatString="~/Account/OrderDetails.aspx?OrderID={0}"

OrderID 필드를 OrderDetails.aspx 페이지에 대한 QueryString 매개 변수로 지정하는 각 제품의 주문 세부 정보 보기에 대한 링크를 지정합니다.

OrderDetails.aspx

EntityDataSource 컨트롤을 사용하여 Orders 및 FormView에 액세스하여 주문 데이터를 표시하고 GridView가 있는 다른 EntityDataSource를 사용하여 모든 주문의 품목을 표시합니다.

<asp:FormView ID="FormView1" runat="server" CellPadding="4" 
                             DataKeyNames="OrderID" 
                             DataSourceID="EDS_Order" ForeColor="#333333" Width="250px">
   <FooterStyle BackColor="#990000" Font-Bold="True" ForeColor="White" />
   <HeaderStyle BackColor="#990000" Font-Bold="True" ForeColor="White" />
   <ItemTemplate>
      OrderID : <%# Eval("OrderID") %><br />
      CustomerName : <%# Eval("CustomerName") %><br />
      Order Date : <%# Eval("OrderDate") %><br />
      Ship Date : <%# Eval("ShipDate") %><br />
   </ItemTemplate>
   <PagerStyle BackColor="#FFCC66" ForeColor="#333333" HorizontalAlign="Center" />
   <RowStyle BackColor="#FFFBD6" ForeColor="#333333" />
</asp:FormView>
<asp:EntityDataSource ID="EDS_Order" runat="server"  EnableFlattening="False" 
                      ConnectionString="name=CommerceEntities" 
                      DefaultContainerName="CommerceEntities" 
                      EntitySetName="Orders" 
                      AutoGenerateWhereClause="True" 
                      Where="" 
                      EntityTypeFilter="" Select="">
   <WhereParameters>
      <asp:QueryStringParameter Name="OrderID" QueryStringField="OrderID" Type="Int32" />
   </WhereParameters>
</asp:EntityDataSource>

<asp:GridView ID="GridView_OrderDetails" runat="server" 
              AutoGenerateColumns="False" 
              DataKeyNames="ProductID,UnitCost,Quantity" 
              DataSourceID="EDS_OrderDetails" 
              CellPadding="4" GridLines="Vertical" CssClass="CartListItem" 
              onrowdatabound="MyList_RowDataBound" ShowFooter="True" 
              ViewStateMode="Disabled">
   <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:BoundField DataField="Quantity" HeaderText="Quantity" ReadOnly="True" 
                     SortExpression="Quantity" />
     <asp:TemplateField> 
       <HeaderTemplate>Item Total</HeaderTemplate>
       <ItemTemplate>
         <%# (Convert.ToDouble(Eval("Quantity")) *  Convert.ToDouble(Eval("UnitCost")))%>
       </ItemTemplate>
     </asp:TemplateField>
   </Columns>
   <FooterStyle CssClass="CartListFooter"/>
   <HeaderStyle  CssClass="CartListHead" />
 </asp:GridView> 
 <asp:EntityDataSource ID="EDS_OrderDetails" runat="server" 
                       ConnectionString="name=CommerceEntities" 
                       DefaultContainerName="CommerceEntities" 
                       EnableFlattening="False" 
                       EntitySetName="VewOrderDetails" 
                       AutoGenerateWhereClause="True" 
                       Where="">
   <WhereParameters>
     <asp:QueryStringParameter Name="OrderID" QueryStringField="OrderID" Type="Int32" />
   </WhereParameters>
</asp:EntityDataSource>

코드 숨김 파일(OrderDetails.aspx.cs)에는 두 개의 작은 하우스키핑 비트가 있습니다.

먼저 OrderDetails가 항상 OrderId를 가져오는지 확인해야 합니다.

protected void Page_Load(object sender, EventArgs e)
{
  if (String.IsNullOrEmpty(Request.QueryString["OrderId"]))
     {
     Response.Redirect("~/Account/OrderList.aspx");
     }
}

또한 품목의 주문 합계를 계산하고 표시해야 합니다.

decimal _CartTotal = 0;

protected void MyList_RowDataBound(object sender, GridViewRowEventArgs e)
{
  if (e.Row.RowType == DataControlRowType.DataRow)
     {
     TailspinSpyworks.Data_Access.VewOrderDetail myCart = new 
                                                        Data_Access.VewOrderDetail();
     myCart = (TailspinSpyworks.Data_Access.VewOrderDetail)e.Row.DataItem;
     _CartTotal += Convert.ToDecimal(myCart.UnitCost * myCart.Quantity);
     }
   else if (e.Row.RowType == DataControlRowType.Footer)
     {
     e.Row.Cells[5].Text = "Total: " + _CartTotal.ToString("C");
   }
}

홈페이지

Default.aspx 페이지에 일부 정적 콘텐츠를 추가해 보겠습니다.

먼저 "Content" 폴더를 만들고 그 안에 Images 폴더를 만듭니다(그리고 홈페이지에 사용할 이미지를 포함합니다.)

Default.aspx 페이지의 아래쪽 자리 표시자에 다음 태그를 추가합니다.

<h2>
  <asp:LoginView ID="LoginView_VisitorGreeting" runat="server">
    <AnonymousTemplate>
       Welcome to the Store !
    </AnonymousTemplate>
    <LoggedInTemplate>
      Hi <asp:LoginName ID="LoginName_Welcome" runat="server" />. Thanks for coming back. 
    </LoggedInTemplate>
  </asp:LoginView>
</h2>

<p><strong>TailSpin Spyworks</strong> demonstrates how extraordinarily simple it is to create powerful, scalable applications for the .NET platform. </p>
<table>
  <tr>
    <td>               
      <h3>Some Implementation Features.</h3>
      <ul>
                <li><a href="#">CSS Based Design.</a></li>
                <li><a href="#">Data Access via Linq to Entities.</a></li>
                <li><a href="#">MasterPage driven design.</a></li>
                <li><a href="#">Modern ASP.NET Controls User.</a></li>
                <li><a href="#">Integrated Ajac Control Toolkit Editor.</a></li>
        </ul>
    </td>
    <td>
        <img src="Content/Images/SampleProductImage.gif" alt=""/>
    </td>
  </tr>
</table>
    
<table>
  <tr>
    <td colspan="2"><hr /></td>
  </tr>
  <tr>
    <td>               
        <!-- Popular Items -->
    </td>
    <td>  
      <center><h3>Ecommerce the .NET 4 Way</h3></center>
      <blockquote>
        <p>
        ASP.NET offers web developers the benefit of more that a decade of innovation. 
        This   demo leverages many of the latest features of ASP.NET development to     
        illustrate really simply building rich web applications with ASP.NET can be. 
        For more information about build web applications with ASP.NET please visit the 
        community web site at www.asp.net
        </p>
      </blockquote>
    </td>
  </tr>
</table>

<h3>Spyworks Event Calendar</h3>
<table>
  <tr class="rowH">
    <th>Date</th>
    <th>Title</th>
    <th>Description</th>
  </tr>
  <tr class="rowA">
    <td>June 01, 2011</td>
    <td>Sed vestibulum blandit</td>
    <td>
    Come and check out demos of all the newest Tailspin Spyworks products and experience 
    them hands on.
    </td>
  </tr>
  <tr class="rowB">
    <td>November 28, 2011</td>
    <td>Spyworks Product Demo</td>
    <td>
    Come and check out demos of all the newest Tailspin Spyworks products and experience 
    them hands on.
    </td>
  </tr>
  <tr class="rowA">
    <td>November 23, 2011</td>
    <td>Spyworks Product Demo</td>
    <td>
     Come and check out demos of all the newest Tailspin Spyworks products and experience 
     them hands on.
    </td>
  </tr>
  <tr class="rowB">
    <td>November 21, 2011</td>
    <td>Spyworks Product Demo</td>
    <td>
    Come and check out demos of all the newest Tailspin Spyworks products and experience 
    them hands on.
    </td>
  </tr>
</table>

제품 리뷰

먼저 제품 검토를 입력하는 데 사용할 수 있는 양식에 대한 링크가 있는 단추를 추가합니다.

<div class="SubContentHead">Reviews</div><br />
<a id="ReviewList_AddReview" href="ReviewAdd.aspx?productID=<%# Eval("ProductID") %>">
   <img id="Img2" runat="server" 
        src="~/Styles/Images/review_this_product.gif" alt="" />
</a>

링크 위치를 보여 주는 스크린샷

쿼리 문자열에서 ProductID를 전달합니다.

다음으로 ReviewAdd.aspx라는 페이지를 추가해 보겠습니다.

이 페이지에서는 ASP.NET AJAX 컨트롤 도구 키트를 사용합니다. 아직 완료하지 않은 경우 DevExpress 에서 다운로드할 수 있으며 여기서 Visual Studio에서 사용할 도구 키트를 설정하는 방법에 대한 지침이 있습니다 https://www.asp.net/learn/ajax-videos/video-76.aspx.

디자인 모드에서는 도구 상자에서 컨트롤 및 유효성 검사기를 끌어 아래와 같은 양식을 작성합니다.

양식을 보여 주는 스크린샷

태그는 다음과 같이 표시됩니다.

<asp:ToolkitScriptManager ID="ToolkitScriptManager1" runat="server">
</asp:ToolkitScriptManager>
<div class="ContentHead">Add Review - <asp:label id="ModelName" runat="server" /></div>
<div>
  <span class="NormalBold">Name</span><br />
  <asp:TextBox id="Name" runat="server" Width="400px" /><br />
  <asp:RequiredFieldValidator runat="server" id="RequiredFieldValidator1" 
                              ControlToValidate="Name" 
                              Display="Dynamic" 
                              CssClass="ValidationError" 
                              ErrorMessage="'Name' must not be left blank."  /><br />
  <span class="NormalBold">Email</span><br />
  <asp:TextBox id="Email" runat="server" Width="400px" /><br />
  <asp:RequiredFieldValidator runat="server" id="RequiredFieldValidator2" 
                              ControlToValidate="Email" Display="Dynamic" 
                              CssClass="ValidationError" 
                              ErrorMessage="'Email' must not be left blank." />
  <br /><hr /><br />
  <span class="NormalBold">Rating</span><br /><br />
  <asp:RadioButtonList ID="Rating" runat="server">
    <asp:ListItem value="5" selected="True" 
             Text='<img src="Styles/Images/reviewrating5.gif" alt=""> (Five Stars) '  />
    <asp:ListItem value="4" selected="True" 
             Text='<img src="Styles/Images/reviewrating4.gif" alt=""> (Four Stars) '  />
    <asp:ListItem value="3" selected="True" 
             Text='<img src="Styles/Images/reviewrating3.gif" alt=""> (Three Stars) '  />
    <asp:ListItem value="2" selected="True" 
             Text='<img src="Styles/Images/reviewrating2.gif" alt=""> (Two Stars) '  />
    <asp:ListItem value="1" selected="True" 
             Text='<img src="Styles/Images/reviewrating1.gif" alt=""> (One Stars) '  />
  </asp:RadioButtonList>
  <br /><hr /><br />
  <span class="NormalBold">Comments</span><br />
  <cc1:Editor ID="UserComment" runat="server" />
  <asp:RequiredFieldValidator runat="server" id="RequiredFieldValidator3" 
                              ControlToValidate="UserComment" Display="Dynamic" 
                              CssClass="ValidationError" 
                              ErrorMessage="Please enter your comment." /><br /><br />
  <asp:ImageButton ImageURL="Styles/Images/submit.gif" runat="server" 
                   id="ReviewAddBtn" onclick="ReviewAddBtn_Click" />
  <br /><br /><br />
</div>

이제 리뷰를 입력할 수 있으므로 제품 페이지에 해당 리뷰를 표시할 수 있습니다.

ProductDetails.aspx 페이지에 이 태그를 추가합니다.

<asp:ListView ID="ListView_Comments" runat="server" 
              DataKeyNames="ReviewID,ProductID,Rating" DataSourceID="EDS_CommentsList">
  <ItemTemplate>
    <tr>
      <td><%# Eval("CustomerName") %></td>
      <td>
        <img src='Styles/Images/ReviewRating_d<%# Eval("Rating") %>.gif' alt="">
        <br />
      </td>
      <td>
        <%# Eval("Comments") %>
      </td>
    </tr>
  </ItemTemplate>
  <AlternatingItemTemplate>
    <tr>
      <td><%# Eval("CustomerName") %></td>
      <td>
        <img src='Styles/Images/ReviewRating_da<%# Eval("Rating") %>.gif' alt="">
        <br />
      </td>
      <td><%# Eval("Comments") %></td>
    </tr>
  </AlternatingItemTemplate>
   <EmptyDataTemplate>
     <table runat="server">
       <tr><td>There are no reviews yet for this product.</td></tr>
     </table>
  </EmptyDataTemplate>
  <LayoutTemplate>
    <table runat="server">
      <tr runat="server">
        <td runat="server">
          <table ID="itemPlaceholderContainer" runat="server" border="1">
            <tr runat="server">
              <th runat="server">Customer</th>
              <th runat="server">Rating</th>
              <th runat="server">Comments</th>
             </tr>
             <tr ID="itemPlaceholder" runat="server"></tr>
           </table>
         </td>
       </tr>
       <tr runat="server">
         <td runat="server">
           <asp:DataPager ID="DataPager1" runat="server">
             <Fields>
               <asp:NextPreviousPagerField ButtonType="Button" 
                                           ShowFirstPageButton="True"
                                           ShowLastPageButton="True" />
             </Fields>
           </asp:DataPager>
         </td>
       </tr>
     </table>
  </LayoutTemplate>
</asp:ListView>
<asp:EntityDataSource ID="EDS_CommentsList" runat="server"  EnableFlattening="False" 
                       AutoGenerateWhereClause="True" 
                       EntityTypeFilter="" 
                       Select="" Where=""
                       ConnectionString="name=CommerceEntities" 
                       DefaultContainerName="CommerceEntities" 
                       EntitySetName="Reviews">
   <WhereParameters>
    <asp:QueryStringParameter Name="ProductID" QueryStringField="productID"  
                                               Type="Int32" />
  </WhereParameters>
</asp:EntityDataSource>

지금 애플리케이션을 실행하고 제품으로 이동하면 고객 리뷰를 포함한 제품 정보가 표시됩니다.

고객 리뷰를 보여 주는 스크린샷

인기 있는 항목 컨트롤(사용자 컨트롤 만들기)

웹 사이트의 매출을 늘리기 위해 인기 있는 제품 또는 관련 제품에 몇 가지 기능을 추가합니다.

이러한 기능 중 첫 번째는 제품 카탈로그에서 가장 인기 있는 제품 목록입니다.

애플리케이션의 홈페이지에 상위 판매 항목을 표시하는 "사용자 컨트롤"을 만듭니다. 이 컨트롤은 컨트롤이므로 Visual Studio 디자이너의 컨트롤을 끌어서 좋아하는 페이지에 놓기만 하면 모든 페이지에서 사용할 수 있습니다.

Visual Studio의 솔루션 탐색기에서 솔루션 이름을 마우스 오른쪽 단추로 클릭하고 "Controls"라는 새 디렉터리를 만듭니다. 이렇게 할 필요는 없지만 "Controls" 디렉터리에 모든 사용자 컨트롤을 만들어 프로젝트를 구성하도록 도와줍니다.

컨트롤 폴더를 마우스 오른쪽 단추로 클릭하고 "새 항목"을 선택합니다.

새 항목을 선택할 위치를 보여 주는 스크린샷

"PopularItems" 컨트롤의 이름을 지정합니다. 사용자 컨트롤의 파일 확장명은 .aspx가 아닌 .ascx입니다.

인기 항목 사용자 컨트롤은 다음과 같이 정의됩니다.

<%@ OutputCache Duration="3600" VaryByParam="None" %>
<div class="MostPopularHead">Our most popular items this week</div>
<div id="PanelPopularItems" runat="server">
  <asp:Repeater ID="RepeaterItemsList" runat="server">
    <HeaderTemplate></HeaderTemplate>
      <ItemTemplate>               
        <a class='MostPopularItemText' 
           href='ProductDetails.aspx?productID=<%# Eval("ProductId") %>'>
                                               <%# Eval("ModelName") %></a><br />              
      </ItemTemplate>
    <FooterTemplate></FooterTemplate>
  </asp:Repeater>
</div>

여기서는 이 애플리케이션에서 아직 사용하지 않은 메서드를 사용합니다. 반복기 컨트롤을 사용하고 있으며 데이터 원본 컨트롤을 사용하는 대신 반복기 컨트롤을 LINQ to Entities 쿼리의 결과에 바인딩합니다.

컨트롤의 뒤에 있는 코드에서는 다음과 같이 이 작업을 수행합니다.

using TailspinSpyworks.Data_Access;

protected void Page_Load(object sender, EventArgs e)
{
  using (CommerceEntities db = new CommerceEntities())
    {
    try
      {
      var query = (from ProductOrders in db.OrderDetails
                        join SelectedProducts in db.Products on ProductOrders.ProductID  
                        equals SelectedProducts.ProductID
                        group ProductOrders by new
                            {
                            ProductId = SelectedProducts.ProductID,
                            ModelName = SelectedProducts.ModelName
                            } into grp
                        select new
                            {
                            ModelName = grp.Key.ModelName,
                            ProductId = grp.Key.ProductId,
                            Quantity = grp.Sum(o => o.Quantity)
                            } into orderdgrp where orderdgrp.Quantity > 0 
                        orderby orderdgrp.Quantity descending select orderdgrp).Take(5);

                    RepeaterItemsList.DataSource = query;
                    RepeaterItemsList.DataBind(); 
      }
    catch (Exception exp)
      {
      throw new Exception("ERROR: Unable to Load Popular Items - " + 
                           exp.Message.ToString(), exp);
      }
    }
}

컨트롤의 태그 맨 위에 있는 이 중요한 줄도 확인합니다.

<%@ OutputCache Duration="3600" VaryByParam="None" %>

가장 인기 있는 항목은 1분에서 1분 단위로 변경되지 않으므로 애플리케이션의 성능을 향상시키기 위해 aching 지시문을 추가할 수 있습니다. 이 지시문은 컨트롤의 캐시된 출력이 만료될 때만 컨트롤 코드가 실행되도록 합니다. 그렇지 않으면 캐시된 버전의 컨트롤 출력이 사용됩니다.

이제 Default.aspx 페이지에 새 컨트롤을 포함하기만 하면됩니다.

끌어서 놓기를 사용하여 컨트롤의 instance 기본 양식의 열린 열에 배치합니다.

컨트롤의 instance 배치할 위치를 보여 주는 스크린샷

이제 애플리케이션을 실행할 때 홈페이지에 가장 인기 있는 항목이 표시됩니다.

홈페이지에서 가장 인기 있는 항목을 표시하는 방법을 보여 주는 스크린샷

"또한 구매됨" 컨트롤(매개 변수가 있는 사용자 컨트롤)

두 번째 사용자 컨트롤을 만들면 컨텍스트 특이성을 추가하여 암시적인 판매를 다음 단계로 끌어올 수 있습니다.

상위 "구매됨" 항목을 계산하는 논리는 간단하지 않습니다.

"또한 구매" 컨트롤은 현재 선택한 ProductID에 대해 OrderDetails 레코드(이전에 구매됨)를 선택하고 발견된 각 고유 주문에 대한 OrderID를 가져옵니다.

그런 다음 모든 주문에서 제품을 선택하고 구매한 수량을 합산합니다. 제품을 해당 수량 합계로 정렬하고 상위 5개 항목을 표시합니다.

이 논리의 복잡성을 고려하여 이 알고리즘을 저장 프로시저로 구현합니다.

저장 프로시저의 T-SQL은 다음과 같습니다.

ALTER PROCEDURE dbo.SelectPurchasedWithProducts
 @ProductID int
AS
        SELECT  TOP 5 
    OrderDetails.ProductID,
    Products.ModelName,
    SUM(OrderDetails.Quantity) as TotalNum

FROM    
    OrderDetails
  INNER JOIN Products ON OrderDetails.ProductID = Products.ProductID

WHERE   OrderID IN 
(
    /* This inner query should retrieve all orders that have contained the productID */
    SELECT DISTINCT OrderID 
    FROM OrderDetails
    WHERE ProductID = @ProductID
)
AND OrderDetails.ProductID != @ProductID 

GROUP BY OrderDetails.ProductID, Products.ModelName 

ORDER BY TotalNum DESC
RETURN

이 저장 프로시저(SelectPurchasedWithProducts)는 애플리케이션에 포함할 때 데이터베이스에 존재했으며 엔터티 데이터 모델을 생성할 때 필요한 테이블 및 뷰 외에도 엔터티 데이터 모델에 이 저장 프로시저를 포함해야 한다고 지정했습니다.

엔터티 데이터 모델에서 저장 프로시저에 액세스하려면 함수를 가져와야 합니다.

솔루션 Explorer 엔터티 데이터 모델을 두 번 클릭하여 디자이너에서 열고 모델 브라우저를 연 다음 디자이너를 마우스 오른쪽 단추로 클릭하고 "함수 가져오기 추가"를 선택합니다.

함수 가져오기 추가를 선택할 위치를 보여 주는 스크린샷

이렇게 하면 이 대화 상자가 열립니다.

열려 있는 대화 상자를 보여 주는 스크린샷

위와 같이 필드를 채우고 "SelectPurchasedWithProducts"를 선택하고 가져온 함수의 이름에 프로시저 이름을 사용합니다.

"확인"을 클릭합니다.

이렇게 하면 모델의 다른 항목과 마찬가지로 저장 프로시저에 대해 프로그래밍할 수 있습니다.

따라서 "Controls" 폴더에서 AlsoPurchased.ascx라는 새 사용자 컨트롤을 만듭니다.

이 컨트롤의 태그는 PopularItems 컨트롤에 매우 친숙해 보입니다.

<div>
<div class="MostPopularHead">
<asp:Label ID="LabelTitle" runat="server" Text=" Customers who bought this also bought:"></asp:Label></div>
<div id="PanelAlsoBoughtItems" runat="server">
    <asp:Repeater ID="RepeaterItemsList" runat="server">
       <HeaderTemplate></HeaderTemplate>
          <ItemTemplate>               
             <a class='MostPopularItemText' href='ProductDetails.aspx?productID=<%# Eval("ProductId") %>'><%# Eval("ModelName") %></a><br />              
          </ItemTemplate>
       <FooterTemplate></FooterTemplate>
    </asp:Repeater>
</div>
</div>

주목할 만한 차이점은 렌더링할 항목이 제품마다 다르기 때문에 출력을 캐싱하지 않는다는 것입니다.

ProductId는 컨트롤의 "속성"이 됩니다.

private int _ProductId;

public int ProductId
{
get { return _ProductId ; }
set { _ProductId = Convert.ToInt32(value); }
}

컨트롤의 PreRender 이벤트 처리기에서 세 가지 작업을 수행합니다.

  1. ProductID가 설정되어 있는지 확인합니다.
  2. 현재 제품과 함께 구매한 제품이 있는지 확인합니다.
  3. #2에서 결정된 대로 일부 항목을 출력합니다.

모델을 통해 저장 프로시저를 호출하는 것이 얼마나 쉬운지 확인합니다.

//--------------------------------------------------------------------------------------+
protected void Page_PreRender(object sender, EventArgs e)
{
  if (_ProductId < 1)
     {
     // This should never happen but we could expand the use of this control by reducing 
     // the dependency on the query string by selecting a few RANDOME products here. 
     Debug.Fail("ERROR : The Also Purchased Control Can not be used without 
                         setting the ProductId.");
     throw new Exception("ERROR : It is illegal to load the AlsoPurchased COntrol 
                                  without setting a ProductId.");
     }
      
  int ProductCount = 0;
  using (CommerceEntities db = new CommerceEntities())
    {
    try
      {
      var v = db.SelectPurchasedWithProducts(_ProductId);
      ProductCount = v.Count();
      }
    catch (Exception exp)
      {
      throw new Exception("ERROR: Unable to Retrieve Also Purchased Items - " + 
                                  exp.Message.ToString(), exp);
      }
    }

  if (ProductCount > 0)
     {
     WriteAlsoPurchased(_ProductId);              
     }
  else
     {
     WritePopularItems();
     }
}

"또한 구매됨"이 있다고 판단한 후에는 단순히 반복기를 쿼리에서 반환된 결과에 바인딩할 수 있습니다.

//-------------------------------------------------------------------------------------+
private void WriteAlsoPurchased(int currentProduct)
{
  using (CommerceEntities db = new CommerceEntities())
        {
        try
          {
          var v = db.SelectPurchasedWithProducts(currentProduct);
          RepeaterItemsList.DataSource = v;
          RepeaterItemsList.DataBind();
          }
         catch (Exception exp)
          {
          throw new Exception("ERROR: Unable to Write Also Purchased - " + 
                                                          exp.Message.ToString(), exp);
          }
        }
}

"구매한" 항목이 없는 경우 카탈로그에서 인기 있는 다른 항목을 표시하기만 하면 됩니다.

//--------------------------------------------------------------------------------------+
private void WritePopularItems()
{
  using (CommerceEntities db = new CommerceEntities())
    {
    try
      {
      var query = (from ProductOrders in db.OrderDetails
                   join SelectedProducts in db.Products on ProductOrders.ProductID 
                   equals SelectedProducts.ProductID
                   group ProductOrders by new
                         {
                         ProductId = SelectedProducts.ProductID,
                         ModelName = SelectedProducts.ModelName
                         } into grp
                   select new
                         {
                         ModelName = grp.Key.ModelName,
                         ProductId = grp.Key.ProductId,
                         Quantity = grp.Sum(o => o.Quantity)
                         } into orderdgrp
                   where orderdgrp.Quantity > 0
                   orderby orderdgrp.Quantity descending
                   select orderdgrp).Take(5);
                   
      LabelTitle.Text = "Other items you might be interested in: ";
      RepeaterItemsList.DataSource = query;
      RepeaterItemsList.DataBind();
      }
    catch (Exception exp)
      {
      throw new Exception("ERROR: Unable to Load Popular Items - " + 
                                                        exp.Message.ToString(), exp);
      }
    }
}

"구매한 항목도"를 보려면 ProductDetails.aspx 페이지를 열고 솔루션 Explorer AlsoPurchased 컨트롤을 끌어 태그의 이 위치에 표시됩니다.

<table  border="0">
  <tr>
     <td>
       <img src='Catalog/Images/<%# Eval("ProductImage") %>'  border="0" 
                                                   alt='<%# Eval("ModelName") %>' />
     </td>
     <td><%# Eval("Description") %><br /><br /><br />  
         <uc1:AlsoPurchased ID="AlsoPurchased1" runat="server" />                 
     </td>
   </tr>
</table>

이렇게 하면 ProductDetails 페이지 맨 위에 컨트롤에 대한 참조가 생성됩니다.

<%@ Register src="Controls/AlsoPurchased.ascx" tagname="AlsoPurchased" tagprefix="uc1" %>

또한Purchased 사용자 컨트롤에는 ProductId 번호가 필요하므로 페이지의 현재 데이터 모델 항목에 대해 Eval 문을 사용하여 컨트롤의 ProductID 속성을 설정합니다.

제품 ID를 강조 표시하는 스크린샷

지금 빌드하고 실행하고 제품으로 이동하면 "또한 구매한" 항목이 표시됩니다.

구매한 항목을 보여 주는 스크린샷