Поделиться через


Часть 5. Бизнес-логика

Джо Стагнер

Tailspin Spyworks демонстрирует, насколько просто создавать мощные масштабируемые приложения для платформы .NET. Здесь показано, как использовать новые функции в ASP.NET 4 для создания интернет-магазина, включая покупки, оформления заказа и администрирования.

В этой серии учебников подробно описаны все шаги по созданию примера приложения Tailspin Spyworks. Часть 5 добавляет некоторую бизнес-логику.

Добавление бизнес-логики

Мы хотим, чтобы наш опыт покупок был доступен всякий раз, когда кто-то посещает наш веб-сайт. Посетители смогут просматривать и добавлять элементы в корзину, даже если они не зарегистрированы или не вошли в систему. Когда они будут готовы проверка, им будет предоставлена возможность пройти проверку подлинности, а если они еще не являются участниками, они смогут создать учетную запись.

Это означает, что нам потребуется реализовать логику для преобразования корзины покупок из анонимного состояния в состояние "Зарегистрированный пользователь".

Давайте создадим каталог с именем Classes, а затем Right-Click в папке и создадим файл Class с именем MyShoppingCart.cs.

Снимок экрана: новый файл класса с именем My Shopping Cart dot 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)
        {

        }
    }
}

Обратите внимание на использование частичного ключевое слово.

Файл класса, который мы только что создали, выглядит следующим образом.

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

namespace TailspinSpyworks.Classes
{
    public class MyShoppingCart
    {
    }
}

Мы объединим наши реализации, добавив частичную ключевое слово в этот файл.

Наш новый файл класса теперь выглядит следующим образом.

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, чтобы узнать, есть ли товар в корзине. Если это так, мы обновляем количество заказа элемента, в противном случае мы создадим новую запись для выбранного элемента.

Чтобы вызвать этот метод, мы реализуем страницу AddToCart.aspx, которая не только классует этот метод, но затем отображает текущую покупку a=cart после добавления элемента.

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>

Вызовите форму в конструкторе, чтобы можно было дважды щелкнуть кнопку Обновить корзину и создать обработчик событий щелчка, указанный в объявлении в разметке.

Мы реализуем подробные сведения позже, но это позволит нам создавать и запускать приложение без ошибок.

При запуске приложения и добавлении элемента в корзину вы увидите это.

Снимок экрана: обновленная корзина для покупок.

Обратите внимание, что мы отклонились от отображения сетки по умолчанию, реализовав три настраиваемых столбца.

Первый — это редактируемое поле "Привязано" для параметра 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>

Снимок экрана: обновленное количество и удаление элементов.

Как видите, строка "Итог по заказу" пуста, поэтому давайте добавим логику для вычисления всего заказа.

Сначала мы реализуем метод GetTotal в классе MyShoppingCart.

В файле 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. В то же время мы добавим тест, чтобы проверить, пуста ли корзина, и соответствующим образом настроим дисплей, если это так.

Теперь, если корзина пуста, мы получаем следующее:

Снимок экрана: пустая корзина.

А если нет, то мы видим нашу общую сумму.

Снимок экрана: общая сумма товаров в корзине.

Однако эта страница еще не завершена.

Нам потребуется дополнительная логика для пересчета корзины покупок путем удаления элементов, помеченных для удаления, и путем определения новых значений количества, так как некоторые из них могли быть изменены в сетке пользователем.

Давайте добавим метод RemoveItem в класс корзины для покупок в Файле MyShoppingCart.cs, чтобы обработать случай, когда пользователь помечает элемент для удаления.

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

Обратите внимание, что этот метод ожидает два параметра. Один из них — это идентификатор корзины для покупок, а другой — массив объектов определяемого пользователем типа.

Чтобы свести к минимуму зависимость нашей логики от особенностей пользовательского интерфейса, мы определили структуру данных, которую можно использовать для передачи элементов корзины в код без прямого доступа к элементу управления 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 создадим базу данных членства и добавим пользователя в репозиторий членства.