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

编写Orchard网上商店模块(2) - 创建ProductPart


上一篇  编写Orchard网上商店模块(1) - 创建Orchard.Webshop模块项目
定义的ProductPart
这是从头开始编写一个新的Orchard模块的教程的第2篇。
本章所讲的内容,为开发模块最基础的构架部分,请按照教程认真学习,这样才能更好的理解模块的开发;
Orchard中的内容项(Content Items)由Content Parts组成。使用我们的网上商店模块,我们希望管理员可以通过把ProductPart附加到任何内容类型(ContentType)上,组成商品(Product)。
例如,用户可以定义一个“书”的内容类型,附加上ProductPart,就把“书”转化成了“商品”,然后就可以添加到购物车和订单及其明细中。
创建一个ProductPart,从而可以存储数据到数据库中,我们需要创建ProductRecord,并继承自Orchard.ContentManagement.Records.ContentPartRecord。
Orchard使用后缀“Record”作为规范,使用NHibernate ORM持久化。
  1. 添加对Orchard.Framework项目的引用,以便能够从Orchard.ContentManagement.Records.ContentPartRecord上继承。
  2. 添加新文件夹命名为”Models”
  3. 在”Models”文件夹中,创建一个新的类命名为ProductRecord。
  4. 在&rdquo;Models&rdquo;文件夹中,创建一个新的类命名为ProductPart,并从ContentPart <ProductRecord>继承。
  5. ProductPart将有以下属性:Price(价格)和SKU。
您的解决方案现在应该是下面的样子的:

ProductRecord.cs:

在Orchard可以映射,加载和保存ProductRecord对象的实例到数据库之前,我们需要告诉它数据库表结构是什么样子。
我们通过创建一个叫Migration的类,一个继承自Orchard.Data.Migration.DataMigrationImpl,并调用一些方法来定义数据库结构(schema)的类。
在Migration里,我们告诉Orchard哪些表要创建和哪些ContenteTypes 和 ContentParts要创建。
在你的模块的跟目录下,创建一个名为Migrations.cs的新类,并敲入如下代码:
using Orchard.Data.Migration;

namespace Orchard.Webshop {
    public class Migrations : DataMigrationImpl {

        public int Create() {
            SchemaBuilder.CreateTable(ProductRecord, table => table
                .ContentPartRecord()
                .Column<decimal>(Price)
                .Column<string>(Sku, column => column.WithLength(50))
                );

            return 1;
        }
    }
}
&ldquo;Create&rdquo;是Orchard使用一个规范,在启用模块时,它将为调用。
ContentPartRecord方法是一种简便的方法,用于创建一个ID列,并设置为主键:
/// <summary>
/// Defines a primary column as for content parts
/// </summary>
public CreateTableCommand ContentPartRecord() {
       Column<int>(Id, column => column.PrimaryKey().NotNull());
       return this;
}
在我们的例子中,我们已经启用了模块。但果Orchard很聪明,它检测到现有一个DataMigration可用, 然后它会检查当前存储的Migration版本号,我们的模块启用时,没有Migration可运行,所以在Orchard_Framework_DataMigrationRecord表也找不相应的版本号:

当您刷新Orchard Admin页面,Orchard将显示一个通知,显示需要升级一些功能:

点击Orchard.Webshop链接,您将直接调转到模块页面,并显示Orchard.Webshop功能:

当您单击&ldquo;Upgrade&rdquo;,Orchard将调用我们的Migrations类的Create方法,从而创建名为Orchard_Webshop_ProductRecord表:


正如你可以看到,Orchard也在Migrations表插入一个新的记录,其中包含了我们的Migration类的类名和它返回的最后一个版本号。
Create方法返回值1。(这里要注意的是:Orchard的版本更新,如果没有提示您更新模块,也没有关系,只要数据库内的字段版本已经与你代码中返回的数字一致就表明他已经更新了
现在我们创建了一个表,可以存储的产品信息,但是这还不够,我们需要告诉Orchard  ProductPart是&ldquo;可以被附加的(attachable)&rdquo;:这是组合ContentTypes的关键,从而使用例子的&ldquo;书&rdquo;可以变成商品。
要做到这一点,我们要在Migration类中添加一个名为UpdateFrom1的方法,使ProductPart成为attachable:
public int UpdateFrom1() {
            ContentDefinitionManager.AlterPartDefinition(typeof(ProductPart).Name, part => part
                .Attachable()
                );

            return 2;
        }
为了能够使用AlterPartDefinition方法和Attachable方法,我们需要导入的命名空间和引用Orchard.Core项目。
小提示:ReSharper的会告诉你哪些程序集要引用和命名空间要引用,在大量的项目中,这还是真挺有帮助的。
完整的migration,现在看起来像这样:
using Orchard.ContentManagement.MetaData;
using Orchard.Core.Contents.Extensions; using Orchard.Data.Migration;  
namespace Orchard.Webshop {
    public class Migrations : DataMigrationImpl {

        public int Create() {
            SchemaBuilder.CreateTable(ProductRecord, table => table
                .ContentPartRecord()
                .Column<decimal>(Price)
                .Column<string>(Sku, column => column.WithLength(50))
                );

            return 1;
        }

        public int UpdateFrom1() {             
 ContentDefinitionManager.AlterPartDefinition(typeof(ProductPart).Name, part => part                 
 .Attachable()
  );
 return 2;
 }
 }
}
当你修改了你的模块,就像我们只是刚刚作的,你只需要保存文件:当你刷新页面,Orchard将重新编译模块。
当你刷新管理页面,Orchard将再次显示一个通知说,一些功能需要进行升级:

我们将继续并更新功能。这样做将导致migration记录的版本号被更新为2,并且使ProductPart成为了attachable:它将在Settings_ContentPartDefinitionRecord表创建一条新的记录,并设置为true:

为了验证,我们有一个ProductPart可用,我们导航到Content -> Content Parts,很神奇,我们干了这么多工作:

很好!现在,让我们继续前进,创建一个新的ContentType,就叫&ldquo;书&rdquo;,并给它附上ProductPart:
创建一个新的内容类型:
  1. 转到Content -> Content Type,并点击&rdquo;Create new Type&rdquo;按钮;
  2. 输入&ldquo;Book&rdquo;作为显示名称和内容类型ID(这个会为你自动完成),然后按&ldquo;Create&rdquo;按钮;
  3. 这个叫&rdquo;Book&rdquo;的内容类型已经创建好了。现在,我们要真正定义这个书的类型,(说到底,任何内容类型都是一组ContentParts的集合), 所以我们的挑选的以下部分,作为书的类型,使它更一个书的商品:Body, Comments, Containable, Product, Route和Tags, 然后点击&ldquo;保存&rdquo;。
现在我们有一个书的Content Type,这也是一种商品类型。我们还附加了Containable Part,这样我们可以把Book添加列表或其他包含Container Part的Content Tyeps中。
由于我们还附上了Route,我们还有了书的标题和URL。Comments让网站的访问者对本书发表评论和Tags Part,允许管理员给书添加标签。
然而,当我们试图建立一个实际的书,我们看到各种输入字段,但是还没有Price和SKU字段:

这是怎么回事?Orchard的工作方式是这样的,为了呈现任何内容,Orchard调用每个ContentPart为Driver。一个Driver很类似与一个MVC Controller,但它只负责处理Part的内容,而不是整个HTTP请求本身。
Driver通常有3个操作方法:一个用于网站的前端显示部分,一个用于该网站的后端显示编辑模式,还有一个处理当管理员保存内容项时的回发(Postback)。
Driver通常返回的ActionResult是一个ShapeResult。ShapeResult告诉Orchard使用Razor模板呈现这个Part,还包含了动态对象将作为的Razor模板的模型。这种模型被称为Shape(形状),是一个动态&rdquo;粘土&rdquo;对象。
因此,一个模板可以被看作是&ldquo;皮肤&rdquo;的形状,使用Rqazor视图(.cshtml)作为实现。形状本身则是Razor视图模型。
在编辑模板的情况下,该模板将包含特定部分内容的编辑字段。
这种Shapes和Drivers的概念可能是Orchard的最强大的功能之一,也是最具挑战性的概念,真正挑战你的大脑(如果你像我一样反应慢,那还真有挑战)。
但实际上,当一旦你看到它是如何工作的,它还是很简单的,让我们继续。
让我们为我们的ProductPart创建Driver程序:
  1. 建一个新的文件夹,起名叫Drivers
  2. 在该文件夹内,创建一个新类名为ProductDriver
ProductDriver类看起来像这样:
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;

namespace Orchard.Webshop.Drivers {
    public class ProductDriver : ContentPartDriver<ProductPart> {
        protected override DriverResult Editor(ProductPart part, dynamic shapeHelper)
        {
            return ContentShape(Parts_Product_Edit, () => shapeHelper.EditorTemplate(TemplateName: Parts/Product, Model: part, Prefix: Prefix));
        }

        protected override DriverResult Editor(ProductPart part, IUpdateModel updater, dynamic shapeHelper)
        {
            updater.TryUpdateModel(part, Prefix, null, null);
            return Editor(part, shapeHelper);
        }
    }
}
Driver程序类,目前有两种方法:编辑(Edit)和处理回发的重载方法。现在我跳过了显示(Display)方法,但我们会稍后补上。
当Orchard想要显示ProductPart的编辑页面进,它调用Driver程序的Edit(编辑)方法。当管理员提交的编辑结果时,重载的Editor(编辑)方法(带IUpdateModel参数)将被调用。
ContentShape方法被定义在ContentPartDriver的基类中,并从MVC ActionResult类继承。一个ContentShape实例告诉Orchard Shape的名称以及这个Shape(形状)看起来像什么:这个Shape有一个TempateName属性,一个Model属性和一个Prefix属性。Orchard要用到这些属性,因此它可以计算出实际使用哪个模板来呈现的形状,并给你一个机会,为要使用的模板提供Model(模型)。
在我们的例子中,我们的形状的名称是&ldquo;Parts_Product_Edit&rdquo;,它的模板放在&ldquo;Parts/Product&rdquo;下就可以被找得到。因为我们在谈论的编辑一个Content Part, Orchard 将在路上加上前缀&ldquo;~//Orchard.Webshop/Views/EditorTemplates&rdquo;,所以完整的路径将是:&ldquo;~/ Orchard.Webshop /Views/EditorTemplates /Parts/Product.cshtml&rdquo;。
然后我们就在那里创建Razor模板文件,它看起来像这样:
@model Orchard.Webshop.Models.ProductPart
<fieldset>
    <legend>Product Fields</legend>

    <div class=editor-label>@Html.LabelFor(x => x.Sku)</div>
    <div class=editor-field>
        @Html.EditorFor(x => x.Sku)
        @Html.ValidationMessageFor(x => x.Sku)
    </div>

    <div class=editor-label>@Html.LabelFor(x => x.Price)</div>
    <div class=editor-field>
        @Html.EditorFor(x => x.Price)
        @Html.ValidationMessageFor(x => x.Price)
    </div>
</fieldset>
在Orchard调用Editor方法返回并实际呈现形状之前,我们需要定义形状的位置。
Placement是一个可以帮助确定在什么位置,和在什么区域(Zone) (其实它也是一个形状) 呈现一定的形状的系统。
我们可以通过在我们的模块项目的根目录下创建一个名为&rdquo;Placement.info&rdquo;的文本文件, 来 定义我们的&ldquo;Parts_Product_Edit&rdquo;(记住,我们在ProductDriver的编辑方法定义的形状的名称),它看起来像这样:
<Placement>
    <Place Parts_Product_Edit=Content:1 />
</Placement>
这是告诉Orchard把任何名为&ldquo;Parts_Product_Edit&rdquo;的形状放置在第二的位置上被称为&ldquo;内容&rdquo;区域中的(第一的位置从0开始,由RoutablePart占用。尝试不同的位置,找一种你认为最好的样子)。
为了提高Razor模板的IntelliSense(智能感知),我们现在应该添加一个web.config项目,以及引用System.Web.Mvc组件(在Orchard源码在lib文件夹下)。
web.config看起来像这样:
<?xml version=1.0?>
<configuration>

  <configSections>
    <sectionGroup name=system.web.webPages.razor type=System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35>
      <remove name=host />
      <remove name=pages />
      <section name=host type=System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35 requirePermission=false />
      <section name=pages type=System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35 requirePermission=false />
    </sectionGroup>
  </configSections>

  <system.web.webPages.razor>
    <host factoryType=System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 />
    <pages pageBaseType=Orchard.Mvc.ViewEngines.Razor.WebViewPage>
      <namespaces>
        <add namespace=System.Web.Mvc />
        <add namespace=System.Web.Mvc.Ajax />
        <add namespace=System.Web.Mvc.Html />
        <add namespace=System.Web.Routing />
        <add namespace=System.Linq/>
        <add namespace=System.Collections.Generic/>
        <add namespace=Orchard.Mvc.Html/>
      </namespaces>
    </pages>
  </system.web.webPages.razor>

  <system.web>
    <compilation targetFramework=4.0>
      <assemblies>
        <add assembly=System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35/>
        <add assembly=System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35/>
        <add assembly=System.Data.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089/>
        <add assembly=System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 />
      </assemblies>
    </compilation>
  </system.web>

</configuration>
当我们现在再添加一本书的内容项时,我们将看到,我们的产品编辑模板像我们预计的一样显示在那里:
让我们来测试并创造3本书:
  • The Hobbit, $50, SKU-1001
  • First Wizards Rule, $39, SKU-1002
  • The Hunger Games, $29, SKU-1003
这的确会创建3个新的书的内容项。然而,当你编辑其中之一,你将看到该Price和SKU是空的!
这是怎么回事儿呢?
问题是,Orchard不知道哪里来存储信息。我们需要在ContentHandler中添加一个StorageFilter(这是类似于MVC的ActionFilter),并使用IRepository <ProductRecord>,保存产品零件。
要创建一个ContentHandler,增加了一个StorageFilter
1。创建一个新文件夹命名为Handlers
2。创建一个新的类名为ProductHandler从ContentHandler的派生
编写以下代码:
using Orchard.ContentManagement.Handlers;
using Orchard.Data;
using Orchard.Webshop.Models;

namespace Orchard.Webshop.Handlers {
    public class ProductHandler : ContentHandler {
        public ProductHandler(IRepository<ProductRecord> repository)
        {
            Filters.Add(StorageFilter.For(repository));
        }
    }
}
我们在这里看到超级棒的依赖注入模式在工作着:Orchard注入了一个ProductRecord的repository到我们的处理程序,我们要作的只是简单地定义我们的类的构造函数包含到它的依赖。
接下来,我们添加了一个StorageFilter到Filters集合中,使Orchard保存和载入我们ProductPart的信息时调用到ProductPart Driver。
当我们尝试更新我们的书籍之一,我们注意到,Price(价格)和SKU字段真的保存了。
总的来说,要创建ContentPart,并能坚持到数据库中,有几个步骤要遵循:
  1. 创建一个Record类来表示你的实体
  2. 创建ContentPart类从ContentPart <TRecord>上派生
  3. 为你的Content Part创建Migration,用它定义数据库结构(schema)
  4. 创建一个为您的Content Part的Driver程序
  5. 创建Content Part的编辑模板
  6. 使用Handler(处理程序),添加Content Part的StorageFilter
有时你可能会发现,你只是想创建一个Content Part, 并不需要保存自定义属性到数据库。例如,Orchard Profile Model定义了ProfilePart, 但没定义ProfilePart类。在这种情况下,你只需要一个步骤:
1. 创建Migration来定义这个Content Part
或者,当你想呈现一个Content Part,你可以定义一个Driver程序,它将创建Shapes(形状)。
再或者,你可以创建一个ShapeTableProvider,简简单单的从Orchard.DisplayManagement.Descriptors.IShapeTableProvider类上派生。
刚开始,这看起来可能有点模糊,直到你找到它的实际需要。我们将很快就会用到的!
原文地址:http://skywalkersoftwaredevelopment.net/blog/writing-an-orchard-webshop-module-from-scratch-part-4
翻译:瑞雪年
下一篇 编写Orchard网上商店模块(3) - 定义ProductCatalog内容类型

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