zoukankan      html  css  js  c++  java
  • Orchard模块开发全接触4:深度改造前台

    这里,我们需要做一些事情,这些事情意味着深度改造前台:

    1:为商品增加 添加到购物车 按钮,点击后功能实现;

    2:商品排序;

    3:购物车预览,以及添加 结算 按钮;

    4:一个显式 购物车中有*个 商品 的widget;

    一:添加到购物车 按钮

    修改 Views/Parts/Product.cshtml:

    @{
        var price = (decimal)Model.Price;
        var sku = (string)Model.Sku;
    }
    <article>
        Price: @price<br />
        Sku: @sku
        <footer>
            <button>Add to shoppingcart</button>
        </footer>
    </article>

    现在,商品列表上已经有了这个按钮:

    image

    包括商品详细页面,也有这个按钮。

    1.1 问题

    问题来了,我们发现,该按钮在商品介绍上面,如果我们想放到下面,该怎么做呢?

    1.2 修改 Display 方法

    protected override DriverResult Display(ProductPart part, string displayType, dynamic shapeHelper)
    {
        //return ContentShape("Parts_Product", () => shapeHelper.Parts_Product(
        //        Price: part.UnitPrice,
        //        Sku: part.Sku
        //    ));
        return Combined(

            // Shape 1: Parts_Product
            ContentShape("Parts_Product", () => shapeHelper.Parts_Product(
                Price: part.UnitPrice,
                Sku: part.Sku
            )),

            // Shape 2: Parts_Product_AddButton
            ContentShape("Parts_Product_AddButton", () => shapeHelper.Parts_Product_AddButton())
            );
    }

    在修改后的方法内,我们创造了一个新的 Shape,叫做 Parts_Product_AddButton,同时,我们使用 Combined 方法,它返回的是依旧是 DriverResult,只不过,它组合了两个形状。

    这个时候,我们知道,需要创建一个 Views/Parts/Product.Addbutton.cshtml 文件:

    <button>@T("Add to shoppingcart")</button>

    当然,我们得把原先的 Product.cshtml 中的代码给恢复过来。

    然后,修改 placement.info,为:

    <Placement>
      <Place Parts_Product_Edit="Content:1" />
      <Place Parts_Product="Content:0" />
      <Place Parts_Product_AddButton="Content:after" />
    </Placement>

    然后,看到不一样了:

    image

    二:实现 添加到购物车 功能

    2.1 前台准备

    首先,我们创建文件夹 Controllers,然后控制器:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Web.Mvc;

    namespace TMinji.Shop.Controllers
    {
        public class ShoppingCartController : Controller
        {

            [HttpPost]
            public ActionResult Add(int id)
            {
                return RedirectToAction("Index");
            }
        }

    }

    然后,修改 Views/Parts/Product.AddButton.cshtml:

    @using (Html.BeginForm("Add", "ShoppingCart", new { id = -1 }))
    {
        <button type="submit">@T("Add to shoppingcart")</button>
    }

    让它变成一个表单,并且指向到 Add 方法。

    注意到,我们这里传递的 id = -1,所以,我们还需要做一件事情,那就是把真是的商品 id 传递过来,才能添加商品。

    于是乎,我们首先应该把 id 传递到 shape 中,修改,我们的 Display 方法,如下:

    protected override DriverResult Display(ProductPart part, string displayType, dynamic shapeHelper)
    {
        //return ContentShape("Parts_Product", () => shapeHelper.Parts_Product(
        //        Price: part.UnitPrice,
        //        Sku: part.Sku
        //    ));
        return Combined(

            // Shape 1: Parts_Product
            ContentShape("Parts_Product", () => shapeHelper.Parts_Product(
                Price: part.UnitPrice,
                Sku: part.Sku
            )),

            // Shape 2: Parts_Product_AddButton
            ContentShape("Parts_Product_AddButton", () => shapeHelper.Parts_Product_AddButton(
                ProductId: part.Id))
            );
    }

    然后,前台 Views/Parts/Product.AddButton.cshtml 如下:

    @{
        var productId = (int)Model.ProductId;
    }
    @using (Html.BeginForm("Add", "ShoppingCart", new { id = productId }))
    {
        <button type="submit">@T("Add to shoppingcart")</button>
    }

    现在,就可以把值传递到控制器了,现在,我们需要实现业务逻辑部分。

    2.2 业务逻辑之 Orchard Service

    在 Models 目录下,增加 ShoppingCartItem 实体类:

    [Serializable]
    public sealed class ShoppingCartItem
    {
        public int ProductId { get; private set; }

        private int _quantity;
        public int Quantity
        {
            get { return _quantity; }
            set
            {
                if (value < 0)
                    throw new IndexOutOfRangeException();

                _quantity = value;
            }
        }

        public ShoppingCartItem()
        {
        }

        public ShoppingCartItem(int productId, int quantity = 1)
        {
            ProductId = productId;
            Quantity = quantity;
        }
    }

    创建 Services 文件夹,然后创建 IShoppingCart 接口,

    public interface IShoppingCart : IDependency
    {
    IEnumerable<ShoppingCartItem> Items { get; }
    void Add(int productId, int quantity = 1);
    void Remove(int productId);
    ProductPart GetProduct(int productId);
    decimal Subtotal();
    decimal Vat();
    decimal Total();
    int ItemCount();
    void UpdateItems();
    }

    然后,其实现类:

    using Orchard;
    using Orchard.ContentManagement;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Web;
    using TMinji.Shop.Models;

    namespace TMinji.Shop.Services
    {
        public class ShoppingCart : IShoppingCart
        {
            private readonly IWorkContextAccessor _workContextAccessor;
            private readonly IContentManager _contentManager;
            public IEnumerable<ShoppingCartItem> Items { get { return ItemsInternal.AsReadOnly(); } }

            private HttpContextBase HttpContext
            {
                get { return _workContextAccessor.GetContext().HttpContext; }
            }

            private List<ShoppingCartItem> ItemsInternal
            {
                get
                {
                    var items = (List<ShoppingCartItem>)HttpContext.Session["ShoppingCart"];

                    if (items == null)
                    {
                        items = new List<ShoppingCartItem>();
                        HttpContext.Session["ShoppingCart"] = items;
                    }

                    return items;
                }
            }

            public ShoppingCart(IWorkContextAccessor workContextAccessor, IContentManager contentManager)
            {
                _workContextAccessor = workContextAccessor;
                _contentManager = contentManager;
            }

            public void Add(int productId, int quantity = 1)
            {
                var item = Items.SingleOrDefault(x => x.ProductId == productId);

                if (item == null)
                {
                    item = new ShoppingCartItem(productId, quantity);
                    ItemsInternal.Add(item);
                }
                else
                {
                    item.Quantity += quantity;
                }
            }

            public void Remove(int productId)
            {
                var item = Items.SingleOrDefault(x => x.ProductId == productId);

                if (item == null)
                    return;

                ItemsInternal.Remove(item);
            }

            public ProductPart GetProduct(int productId)
            {
                return _contentManager.Get<ProductPart>(productId);
            }

            public void UpdateItems()
            {
                ItemsInternal.RemoveAll(x => x.Quantity == 0);
            }

            public decimal Subtotal()
            {
                return Items.Select(x => GetProduct(x.ProductId).UnitPrice * x.Quantity).Sum();
            }

            public decimal Vat()
            {
                return Subtotal() * .19m;
            }

            public decimal Total()
            {
                return Subtotal() + Vat();
            }

            public int ItemCount()
            {
                return Items.Sum(x => x.Quantity);
            }

            private void Clear()
            {
                ItemsInternal.Clear();
                UpdateItems();
            }
        }
    }

    以上代码不再一一解释,相信大家能看明白,然后,相应的,修改控制器吧:

    using Orchard;
    using System;
    using Orchard.Mvc;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Web.Mvc;
    using TMinji.Shop.Services;

    namespace TMinji.Shop.Controllers
    {
        public class ShoppingCartController : Controller
        {

            private readonly IShoppingCart _shoppingCart;
            private readonly IOrchardServices _services;

            public ShoppingCartController(IShoppingCart shoppingCart, IOrchardServices services)
            {
                _shoppingCart = shoppingCart;
                _services = services;
            }

            [HttpPost]
            public ActionResult Add(int id)
            {

                // Add the specified content id to the shopping cart with a quantity of 1.
                _shoppingCart.Add(id, 1);

                // Redirect the user to the Index action (yet to be created)
                return RedirectToAction("Index");
            }

            public ActionResult Index()
            {

                // Create a new shape using the "New" property of IOrchardServices.
                var shape = _services.New.ShoppingCart();

                // Return a ShapeResult
                return new ShapeResult(this, shape);
            }

        }
    }

    在控制器中,我们看到了三点变化:

    1:构造器接受了两个对象,它们是被注入的,这会由 Orchard 完成;

    2:Add 方法可以添加商品到购物车了;

    3:增加了一个 Index 方法。可以看到在这个方法中,我们又创建了一个 Shape,而这个 Shape 的名字叫做 ShoppingCart。注意哦,它和后台创建 Shape 不一样(记得吗,在 Display 方法 中),这个 shape 对于那个的 cshtml 文件为 Views/ShoppingCart.cshtml:

    TODO: display our shopping cart contents!

    如果我们这个时候运行代码,会发现点击 添加到购物车 后,变成 404 。这是因为,我们没有为路由加上 area,它是什么,是模块名,Orchard 会根据这个 area,路由到对于的模块中的 controller。故,我们应该修改 Views/Parts/Product.AddButton.cshtml

    @{
        var productId = (int)Model.ProductId;
    }
    @using (Html.BeginForm("Add", "ShoppingCart", new { id = productId, area = "TMinji.Shop" }))
    {
        <button type="submit">@T("Add to shoppingcart")</button>
    }

    现在,继续运行代码,现在,我们得到错误:

    The required anti-forgery form field "__RequestVerificationToken" is not present.

    当然,这个错误,熟悉 MVC 的我们,已经知道怎么修改了,修改之:

    @{
        var productId = (int)Model.ProductId;
    }
    @using (Html.BeginFormAntiForgeryPost(Url.Action("Add", "ShoppingCart", new { id = productId, area = "TMinji.Shop" })))
    {
        <button type="submit">@T("Add to shoppingcart")</button>
    }

    再次运行:

    image

    我们看到,这个页面没有包含当前的 Theme 的母版页,回到控制器,加一个 ThemedAttribute(当然,我们得 using Orchard.Themes;),如下:

    [Themed]
    public ActionResult Index()
    {
        // Create a new shape using the "New" property of IOrchardServices.
        var shape = _services.New.ShoppingCart();

        // Return a ShapeResult
        return new ShapeResult(this, shape);
    }

    这个时候,再次运行,得到了如下的理想效果:

    image

    总结

    1:前台的展现,主要通过 MVC 的控制器来实现的,而后台则绕开了控制器;

    2:业务逻辑,使用 Service 机制,而不是我们自己瞎写的类和逻辑(当然,也可以),但 Service 机制看上去更 Orchard;

    3:前后台创建 Shape 的方式是不一样的。

  • 相关阅读:
    程序员这口饭职业规划解决方案
    Entity Framework 学习初级篇Entity SQL
    工厂方法模式与IoC/DI 4
    为NUnit制作Visual Studio模板
    Enterprise Architect 7.5(UML工具)集成到Visual Studio中(有下载)
    从事ASP.NET开发两年多,谈谈对两三年工作经验的ASP.NET程序员的基本见解
    模式讲解
    一位软件工程师的7年总结 [转]
    工厂方法模式2
    CodeDom系列1
  • 原文地址:https://www.cnblogs.com/luminji/p/3860188.html
Copyright © 2011-2022 走看看