请注意,本站并不支持低于IE8的浏览器,为了获得最佳效果,请下载最新的浏览器,推荐下载 Chrome浏览器
欢迎光临。交流群:166852192

编写Orchard网上商店模块(5) - 渲染购物车和部件


这是从头开始编写一个新的Orchard模块的教程的第5篇。本章所讲的内容,为开发模块教基础的部分,请按照教程认真学习,这样才能更好的理解模块的开发; 上一篇文章链接地址:编写Orchard网上商店模块(4) - 创建购物车
在这一部分,我们将开发如下功能:
  • 创建一个购物车的显示界面
  • 使我们的用户能够更新商品数量并从购物车中删除商品
  • 创建一个部件,可以在所有页面显示一个“购物车”页面的链接,以及购物车中的商品总数;
  • 此次部分包括如何使用IResourceManifestProvider让我们的模块包含指定的资源(图片、脚本等)
  • 使用KnockoutJSjQuery在客户端上,以使我们的用户可以实时更新的购物车商品的数量,以及实时更新“购物车部件”中的商品数量。 

创建购物车的展示界面

首先,我们将添加一些标记到“ShoppingCart.cshtml”的模板页面,如下图:

我们要显示添加到购物车商品的名称和价格。
第一步是用下面的内容替换“ShoppingCart.cshtml”的标记:
  @{ Style.Require(Webshop.ShoppingCart);} 
  <article class="shoppingcart"> 
   <table>
     
    <thead> 
     <tr> 
      <td>Article</td> 
      <td class="numeric">Quantity</td> 
      <td class="numeric">Price</td> 
      <td></td> 
     </tr> 
    </thead> 
    <tbody>
      @for (var i = 0; i < 5; i++) { 
     <tr> 
      <td>Product title</td> 
      <td class="numeric"><input type="number" value="1" /></td> 
      <td class="numeric">$9.99</td> 
      <td><a class="icon" delete="" href="#"></a></td> 
     </tr> } 
    </tbody> 
    <tfoot> 
     <tr class="separator">
      <td colspan="4">&nbsp;</td>
     </tr> 
     <tr> 
      <td class="numeric" label="" colspan="2">VAT (19%):</td> 
      <td class="numeric">$9.99</td> 
      <td></td> 
     </tr> 
     <tr> 
      <td class="numeric" label="" colspan="2">Total:</td> 
      <td class="numeric">$9.99</td> 
      <td></td> 
     </tr> 
    </tfoot> 
   </table> 
   <footer> 
    <div class="group"> 
     <div class="align" left="">
      <a class="button" href="#">Continue shopping</a>
     </div> 
     <div class="align" right="">
      <a class="button" next="" href="#">Proceed to checkout</a>
     </div> 
    </div> 
   </footer>
  </article>
管理资源



如果您仔细阅读了上面的代码,您可能会注意第二行:
Style.Require(Webshop.ShoppingCart);
这是什么那?是的,他是非常有用的东西:它是Orchard资源管理API的一部分,可以在模块开发中使用,使其所呈现的页面输出添加的样式和脚本。
Style是一种资源在Orchard.Mvc.ViewEngines.Razor.WebViewPage <T>类,当我们认为应当呈现的样式时,我们可以用它来指示Orchard包括的样式表。
Require方法需要一个资源名称,我们将使用下面定义的资源清单
资源清单的设计是作为一个简单的类实现IManifestResourceProvider接口。
我们的文件名为“ResourceManifest.cs”的在项目的根目录创建新类:
ResourceManifest.cs:
using Orchard.UI.Resources;
namespace Orchard.Webshop {
    public class ResourceManifest: IResourceManifestProvider {
        public void BuildManifests(ResourceManifestBuilder builder) {
            var manifest = builder.Add();
            manifest.DefineStyle("Webshop.Common").SetUrl("common.css");
            manifest.DefineStyle("Webshop.ShoppingCart").SetUrl("shoppingcart.css").SetDependencies("Webshop.Common");
        }
    }
}


IResourceManifestProvider只定义了一个方法,称为BuildManifest,接收为ResourceManifestBuilder类型的参数。
我们使用这种生成器创建并添加一个新的清单,这是用来注册我们的资源。
在我们的例子中,我们注册两种资源:“common.css”文件和“shoppingcart.css”文件,后面我们将添加他,按照惯例,orchard也是将样式放在Style的文件夹中存储。
请注意,我们设置了“Webshop.ShoppingCart”资源的的“Webshop.Common”资源依赖。
这使得我们只是引用“Webshop.ShoppingCart”在我们的的“ShoppingCart.cshtml”模板,无需也引用“Webshop.Common”,Orchard自动将包括相关的资源。
现在让我们创建对应的Style文件夹在我们的项目的根目录中,并添加两个css文件:
common.css:
.group .align.left {
	float: left;
}

.group .align.right {
	float: right;
}

.icon {
	display: inline-block;
	width: 16px;
	height: 16px;
	background: url(../images/sprites.png);
}

.icon.edit {
	background-position: -8px -40px;
}

.icon.edit:hover {
	background-position: -40px -40px;
}

.icon.delete {
	background-position: -8px -7px;
}

.icon.delete:hover {
	background-position: -39px -7px;
}


shoppingcart.css:
article.shoppingcart {
	width: 500px;
}

article.shoppingcart table {
	width: 100%;
}

article.shoppingcart td {
	padding: 7px 3px 4px 4px;
}

article.shoppingcart table thead td {
	background: #f6f6f6;
	font-weight: bold;
}

article.shoppingcart table tfoot tr.separator td {
	border-bottom: 1px solid #ccc;
}

article.shoppingcart table tfoot td {
	font-weight: bold;
}

article.shoppingcart footer {
	margin-top: 20px;
}

article.shoppingcart td.numeric {
	width: 75px;
	text-align: right;
}

article.shoppingcart td.numeric input {
	width: 50px;
}


请注意,common.css引用的图像文件为sprites.png,这是在一个名为Images文件夹中存储的。
你可以现在下载它(右键保存到一个新的文件夹,名为图片 ):

让我们来看看我们到目前为止已经完成了功能,如下图

好吧,我可能不是设计师,但是这绝对不是我们期待后的效果。
显然CSS是出于某种原因没有被应用。 让我们来看看,如果有F12键开发工具,可以给我们一个一些提示:

阿哈,发现问题了,这两个css文件都要求都返回一个404错误,即使URL是正确的。

 

让资源可以下载

因此,什么?Orchard的安装配备了一个web.config文件,默认情况下,网站里的物理请求都会被屏蔽。System.Web.HttpNotFoundHandler物理文件的所有请求。 我想究其原因是为了防止访问磁盘上的文件不应该(直接)从外界访问。 显然,我们需要重写我们的风格和图像文件夹。 做到这一点的方式很简单:只需创建一个web.config文件的StyleImages文件夹,并粘贴下面的配置:
<?xml version=1.0 encoding=UTF-8?>

  <configuration> 
   <appsettings> 
    <add key="webpages:Enabled" value="false" /> 
   </appsettings> 
   <system.web> 
    <httphandlers> 
     <!-- iis6 - for any request in this location, return via managed static file handler --> 
     <add path="*" verb="*" type="System.Web.StaticFileHandler" /> 
    </httphandlers> 
   </system.web> 
   <system.webserver> 
    <handlers accesspolicy="Script,Read"> 
     <!--       iis7 - for any request to a file exists on disk, return it via native http module.       accessPolicy 'Script' is to allow for a managed 404 page.       --> 
     <add name="StaticFile" path="*" verb="*" modules="StaticFileModule" precondition="integratedMode" resourcetype="File" requireaccess="Read" /> 
    </handlers> 
   </system.webserver>
  </configuration>
这将有效地映射到物理文件中包含的文件夹的System.Web.StaticFileHandler的请求。
现在,我们不得不这样做自己,但一个命令行工具来创建一个新的模块项目,为我们创造一个基本的骨架,包括“style”、“script”文件夹中包含一个web.config的配置。
现在,让我们刷新购物车页面,如下图:

显示购物车的数据,而不是静态值

下一步就是把它显示购物车的实际内容。
我们可以把ShoppingCart服务实例添加到购物车的形状中,但请记住每个ShoppinCartItem唯一拥有的商品,每只保存ShoppinCartItem ID的商品,而不是产品记录本身。
这将意味着我们需要在我们的渲染代码的数据库查询。
打开ShoppinCartController.cs做修改如下
using System.Linq;
using System.Web.Mvc;
using Orchard.DisplayManagement;
using Orchard.Mvc;
using Orchard.Themes;
using Orchard.Webshop.Services;
namespace Orchard.Webshop.Controllers {
    public class ShoppingCartController: Controller {
        private readonly IShoppingCart _shoppingCart;
        private readonly IShapeFactory _shapeFactory;
        private readonly IOrchardServices _orchardServices;
        public ShoppingCartController(IShoppingCart shoppingCart, IShapeFactory shapeFactory, IOrchardServices orchardServices) {
            _shoppingCart = shoppingCart;
            _shapeFactory = shapeFactory;
            _orchardServices = orchardServices;
        } 
        [HttpPost]
        public ActionResult Add(int id) {
            _shoppingCart.Add(id, 1);
            return RedirectToAction("Index");
        } 
        [Themed]
        public ActionResult Index() {
            dynamic shape = _shapeFactory.Create(ShoppingCart);
            dynamic shapeHelper = _shapeFactory;
            var query = from item in _shoppingCart.Items
             let product = _shoppingCart.GetProduct(item.ProductId) select shapeHelper.ShoppingCartItem(Product: product, ContentItem: _orchardServices.ContentManager.Get(product.ContentItemRecord.Id), Quantity: item.Quantity);
            shape.Items = query.ToArray();
            shape.Total = _shoppingCart.Total();
            shape.Subtotal = _shoppingCart.Subtotal();
            shape.Vat = _shoppingCart.Vat();
            return new ShapeResult(this, shape);
        }
    }
}


有几个有趣的部分,这里要注意:首先,我们在Index 方法使用了_shapeFactory dynamic类型。 我们之所以这样做是因为这样,我们有一个更好的方法来创建动态的形状,你可以在这里看到:


shapeHelper.ShoppingCartItem(


静态类型的版本(_shapeFactory.Create(string name, INamedEnumerable<object> parameters)),需要一个类型INamedEnumerable <obect>的枚举成为形状的属性。
说实话,在这一点上我不知道如何提供列表,但我认为它需要更多的工作,而不是简单地使用动态的方法。
查询每个项目ShoppingCartItem成一个形状称为ShoppingCartItem,它收到以下属性:
  • 产品(Orchard.Webshop.ProductRecord)
  • 内容项(Orchard.ContentManagement.ContentItem)
  • 数量(INT32)


优化查询


现在,咱们说了很多关于这个查询的事情,但你不能它是非常高效。
一方面, 它进行了数据库查询,为每个迭代加载ProductRecord
更糟的是它进行数据库查询每次迭代两次,因为它也装载ContentItem使用导航属性的ContentItem ProductRecord(一会我们会看到为什么我们需要ContentItem)。
让我们看看如果我们可以提高查询,使它只需要进行一次数据库查询,并返回所有的产品和内容项目。
Orchard提供IContentManager的服务,我们可以访问通过IOrchardServices(我们需要一个依赖通过构造函数)。
IContentManager称为GetMany <TContent>的方法,其中TContent被限制为一个类,它实现IContent。 参数期待一个ID枚举。
因为它我们所有的部分内容继承IContent,包括我们自己的ProductPart类(继承从ContentPart,从而实现IContent)的。
让我们修改我们查询采用的GetMany方法:
var ids = _shoppingCart.Items.Select(x = >x.ProductId);
var productParts = _orchardServices.ContentManager.GetMany < ProductPart > (ids, VersionOptions.Latest, QueryHints.Empty).ToArray();
var query = from item in _shoppingCart.Items from product in productParts where product.Id == item.ProductId select shapeHelper.ShoppingCartItem(Product: product, ContentItem: product.ContentItem, Quantity: item.Quantity);
原文:http://skywalkersoftwaredevelopment.net/blog/writing-an-orchard-webshop-module-from-scratch-part-7

这篇文件太长了,先写上半部分。

作者原创内容不容易,如果觉得内容不错,请点击右侧“打赏”,赏俩给作者花花,也算是对作者付出的肯定,也可以鼓励作者原创更多更好内容。
更多详情欢迎到QQ群 166852192 交流。