Udostępnij za pośrednictwem


Część 7. Dodawanie funkcji

Autor : Joe Stagner

Tailspin Spyworks pokazuje, jak niezwykle proste jest tworzenie zaawansowanych, skalowalnych aplikacji dla platformy .NET. Pokazuje, jak używać wspaniałych nowych funkcji w ASP.NET 4 do tworzenia sklepu internetowego, w tym zakupów, wyewidencjonowania i administracji.

W tej serii samouczków szczegółowo opisano wszystkie kroki wykonywane w celu utworzenia przykładowej aplikacji Tailspin Spyworks. Część 7 dodaje dodatkowe funkcje, takie jak przegląd konta, przeglądy produktów i "popularne elementy", a także kontrolki użytkownika "zakupione".

Dodawanie funkcji

Chociaż użytkownicy mogą przeglądać nasz katalog, umieszczać elementy w koszyku i ukończyć proces finalizacji zakupu, dostępnych jest wiele funkcji pomocniczych, które będziemy uwzględniać w celu ulepszenia naszej witryny.

  1. Przegląd konta (zamówienie listy złożone i wyświetlanie szczegółów).
  2. Dodaj pewną zawartość specyficzną dla kontekstu do strony głównej.
  3. Dodaj funkcję, aby umożliwić użytkownikom przeglądanie produktów w katalogu.
  4. Utwórz kontrolkę użytkownika, aby wyświetlić popularne elementy i umieść kontrolkę na stronie głównej.
  5. Utwórz kontrolkę użytkownika "Również zakupione" i dodaj ją do strony szczegółów produktu.
  6. Dodaj stronę kontaktu.
  7. Dodaj stronę Informacje.
  8. Błąd globalny

Przegląd konta

W folderze "Account" utwórz dwie strony aspx o nazwie OrderList.aspx i inne o nazwie OrderDetails.aspx

Funkcja OrderList.aspx będzie korzystać z kontrolek GridView i EntityDataSource tak samo jak poprzednio.

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

Element EntityDataSource wybiera rekordy z tabeli Orders odfiltrowane według parametru UserName (zobacz WhereParameter), które ustawiamy w zmiennej sesji, gdy loguje się użytkownik.

Zwróć również uwagę na następujące parametry w obszarze HyperlinkField kontrolki GridView:

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

Określają one link do widoku Szczegóły zamówienia dla każdego produktu, określając pole OrderID jako parametr QueryString na stronie OrderDetails.aspx.

OrderDetails.aspx

Użyjemy kontrolki EntityDataSource, aby uzyskać dostęp do elementów Orders i FormView w celu wyświetlenia danych zamówienia i innej jednostki EntityDataSource z kontrolką GridView, aby wyświetlić wszystkie elementy wiersza zamówienia.

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

W pliku Code Behind (OrderDetails.aspx.cs) mamy dwa małe fragmenty sprzątania.

Najpierw musimy upewnić się, że element OrderDetails zawsze otrzymuje identyfikator OrderId.

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

Musimy również obliczyć i wyświetlić sumę zamówienia z elementów wiersza.

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

Strona główna

Dodajmy zawartość statyczną do strony Default.aspx.

Najpierw utworzym folder "Zawartość" i w nim folder Images (i dołączę obraz do użycia na stronie głównej).

W dolnym symbolu zastępczym strony Default.aspx dodaj następujący znacznik.

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

Recenzje produktów

Najpierw dodamy przycisk z linkiem do formularza, którego możemy użyć do wprowadzenia przeglądu produktu.

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

Zrzut ekranu przedstawiający lokalizację linku.

Zwróć uwagę, że przekazujemy identyfikator ProductID w ciągu zapytania

Następnie dodajmy stronę o nazwie ReviewAdd.aspx

Ta strona będzie używać zestawu narzędzi ASP.NET AJAX Control Toolkit. Jeśli jeszcze tego nie zrobiono, możesz pobrać go z witryny DevExpress i znajdziesz wskazówki dotyczące konfigurowania zestawu narzędzi do użycia z programem Visual Studio tutaj https://www.asp.net/learn/ajax-videos/video-76.aspx.

W trybie projektowania przeciągnij kontrolki i moduły sprawdzania poprawności z przybornika i skompiluj formularz podobny do poniższego.

Zrzut ekranu przedstawiający formularz.

Znaczniki będą wyglądać mniej więcej tak.

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

Teraz, po wprowadzeniu recenzji, możemy wyświetlić te recenzje na stronie produktu.

Dodaj ten znacznik do strony 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>

Uruchomienie naszej aplikacji teraz i przejście do produktu pokazuje informacje o produkcie, w tym recenzje klientów.

Zrzut ekranu przedstawiający opinie klientów.

Kontrolka Popularne elementy (tworzenie kontrolek użytkownika)

Aby zwiększyć sprzedaż w witrynie internetowej, dodamy kilka funkcji do "sugestywnej sprzedaży" popularnych lub powiązanych produktów.

Pierwsza z tych funkcji będzie listą bardziej popularnych produktów w naszym katalogu produktów.

Utworzymy "Kontrolkę użytkownika", aby wyświetlić elementy najlepiej sprzedających się na stronie głównej naszej aplikacji. Ponieważ będzie to kontrolka, możemy jej użyć na dowolnej stronie, przeciągając i upuszczając kontrolkę w projektancie programu Visual Studio na dowolnej stronie, którą lubimy.

W Eksploratorze rozwiązań programu Visual Studio kliknij prawym przyciskiem myszy nazwę rozwiązania i utwórz nowy katalog o nazwie "Controls". Chociaż nie jest to konieczne, pomożemy utrzymać organizację naszego projektu przez utworzenie wszystkich kontrolek użytkowników w katalogu "Kontrolki".

Kliknij prawym przyciskiem myszy folder controls i wybierz polecenie "Nowy element":

Zrzut ekranu pokazujący, gdzie wybrać pozycję Nowy element.

Określ nazwę kontrolki "PopularItems". Należy pamiętać, że rozszerzenie pliku dla kontrolek użytkownika to .ascx, a nie .aspx.

Nasza kontrolka Popularne elementy Kontrolka użytkownika zostanie zdefiniowana w następujący sposób.

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

W tym miejscu używamy metody, która nie była jeszcze używana w tej aplikacji. Używamy kontrolki powtarzania i zamiast używać kontrolki źródła danych, wiążemy kontrolkę Repeater z wynikami zapytania LINQ to Entities.

W kodzie naszej kontroli robimy to w następujący sposób.

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

Zwróć również uwagę na ten ważny wiersz u góry znaczników kontrolki.

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

Ponieważ najpopularniejsze elementy nie będą zmieniane od minuty do minuty, możemy dodać dyrektywę o buforowaniu, aby poprawić wydajność naszej aplikacji. Ta dyrektywa spowoduje wykonanie kodu kontrolek tylko po wygaśnięciu buforowanych danych wyjściowych kontrolki. W przeciwnym razie zostanie użyta buforowana wersja danych wyjściowych kontrolki.

Teraz wystarczy dołączyć nową kontrolkę na stronie Default.aspx.

Użyj przeciągania i upuszczania, aby umieścić wystąpienie kontrolki w otwartej kolumnie formularza domyślnego.

Zrzut ekranu pokazujący, gdzie umieścić wystąpienie kontrolki.

Teraz, gdy uruchomimy aplikację, strona główna wyświetla najpopularniejsze elementy.

Zrzut ekranu przedstawiający sposób wyświetlania najpopularniejszych elementów na stronie głównej.

Kontrolka "Również zakupione" (kontrolki użytkownika z parametrami)

Druga kontrolka użytkownika, którą utworzymy, podejmie sugerowaną sprzedaż do następnego poziomu, dodając specyfikę kontekstu.

Logika obliczania pierwszych elementów "Również zakupione" nie jest trywialna.

Nasza kontrolka "Również zakupione" wybierze rekordy OrderDetails (wcześniej zakupione) dla aktualnie wybranego identyfikatora ProductID i pobierze identyfikatory OrderID dla każdego znalezionego unikatowego zamówienia.

Następnie wybierzemy wszystkie produkty ze wszystkich tych zamówień i zsumujemy zakupione ilości. Posortujemy produkty według tej sumy ilości i wyświetlimy pięć pierwszych elementów.

Ze względu na złożoność tej logiki zaimplementujemy ten algorytm jako procedurę składowaną.

Język T-SQL dla procedury składowanej jest następujący.

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

Należy pamiętać, że ta procedura składowana (SelectPurchasedWithProducts) istniała w bazie danych, gdy uwzględniliśmy ją w naszej aplikacji, a kiedy wygenerowaliśmy model danych jednostki, określono, że oprócz potrzebnych tabel i widoków model danych jednostki powinien zawierać tę procedurę składowaną.

Aby uzyskać dostęp do procedury składowanej z modelu danych jednostki, musimy zaimportować funkcję.

Kliknij dwukrotnie model danych jednostki w Eksploratorze rozwiązań, aby otworzyć go w projektancie i otworzyć przeglądarkę modelu, a następnie kliknij prawym przyciskiem myszy w projektancie i wybierz pozycję "Dodaj import funkcji".

Zrzut ekranu pokazujący, gdzie wybrać pozycję Dodaj import funkcji.

Spowoduje to otwarcie tego okna dialogowego.

Zrzut ekranu przedstawiający otwarte okno dialogowe.

Wypełnij pola, jak pokazano powyżej, wybierając pozycję "SelectPurchasedWithProducts" i użyj nazwy procedury dla nazwy zaimportowanej funkcji.

Kliknij przycisk "OK".

Po wykonaniu tej czynności możemy po prostu zaprogramować względem procedury składowanej, ponieważ możemy użyć dowolnego innego elementu w modelu.

Dlatego w folderze "Controls" utwórz nową kontrolkę użytkownika o nazwie AlsoPurchased.ascx.

Znaczniki dla tej kontrolki będą wyglądać bardzo znajomo dla kontrolki 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>

Godna uwagi różnica polega na tym, że nie buforuje danych wyjściowych, ponieważ elementy do renderowania będą się różnić w zależności od produktu.

Identyfikator produktu będzie "właściwością" kontrolki.

private int _ProductId;

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

W procedurze obsługi zdarzeń preRender kontrolki wykonaliśmy trzy czynności.

  1. Upewnij się, że ustawiono wartość ProductID.
  2. Sprawdź, czy istnieją jakieś produkty zakupione w ramach bieżącej wersji.
  3. Wyprowadź dane wyjściowe niektórych elementów określonych w #2.

Zwróć uwagę, jak łatwo jest wywołać procedurę składowaną za pomocą modelu.

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

Po określeniu, że istnieje "również zakupione" możemy po prostu powiązać repeater z wynikami zwróconych przez zapytanie.

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

Jeśli nie było żadnych elementów "również zakupionych", po prostu wyświetlimy inne popularne elementy z naszego katalogu.

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

Aby wyświetlić elementy "Również zakupione", otwórz stronę ProductDetails.aspx i przeciągnij kontrolkę AlsoPurchased z Eksploratora rozwiązań, aby była wyświetlana w tej pozycji na znacznikach.

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

Spowoduje to utworzenie odwołania do kontrolki w górnej części strony ProductDetails.

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

Ponieważ kontrolka użytkownika AlsoPurchased wymaga numeru ProductId, ustawimy właściwość ProductID kontrolki przy użyciu instrukcji Eval względem bieżącego elementu modelu danych strony.

Zrzut ekranu przedstawiający wyróżnianie identyfikatora we/wy produktu.

Gdy skompilujemy i uruchomimy teraz i przejdźmy do produktu, zobaczymy elementy "Również zakupione".

Zrzut ekranu przedstawiający również zakupione elementy.