因为公司业务需要,经过本人几个月尝试,使用Orchard CMS 开发一个简单的企业门户(地址是http://www.ubof.cn)。在刚开始接触Orchard CMS,对于个性化的网页布局不知道怎么定义,通过google的帮助终于找到比较符合Orchard的二次开发方法的解决方案,就是在Theme定义布局Widgets Layout。需要注意的是,因为要在Theme写C#代码,所以要用Orchard 命令提示创建Theme的解决方案(命令:codegen theme <theme-name> /CreateProject:true)。
当Theme的解决方案方案建立好后我们就可以开始定义自己的Widgets Layout,在这里我以自己网站上首页的一个产品展示Widgets为例子说明(此为新开发没有同步到发布站点),最后实现好的效果如下:
下面开始介绍代码的编写,第一步需要在刚建好的Theme的解决方案里,新建名称为Providers的文件夹,第二步在建好Providers文件里新建ProductsListLayoutForms的类,它必须实现Orchard里的 IFormProvider 接口,这个类主要作用是为Widgets 中HTML DOM的Id,class 提供配置表单,为以后修改class和id提供方便,具体代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using Orchard.DisplayManagement; using Orchard.Forms.Services; using Orchard.Localization; namespace UBOfficeTheme.Providers.Layouts { public class ProductsListLayoutForms : IFormProvider { protected dynamic Shape { get; set; } public Localizer T { get; set; } public ProductsListLayoutForms(IShapeFactory shapeFactory) { Shape = shapeFactory; T = NullLocalizer.Instance; } public void Describe(DescribeContext context) { Func<IShapeFactory, object> form = shape => { var f = Shape.Form( Id: "ProductsListLayout", _HtmlProperties: Shape.Fieldset( Title: T("Html properties"), _ListId: Shape.TextBox( Id: "culture-id", Name: "CultureId", Title: T("culture id"), Description: T("The id to provide on the culture div element."), Classes: new[] { "textMedium", "tokenized" } ), _ListClass: Shape.TextBox( Id: "culture-classes", Name: "CultureClasses", Title: T("culture classes"), Description: T("The class to provide on the culture div element."), Classes: new[] { "textMedium", "tokenized" } ), _CultureTitle: Shape.TextBox( Id: "culturetitle-classes", Name: "CultureTitleClasses", Title: T("culture title classes"), Description: T("The class to provide on the culturetitle div element."), Classes: new[] { "textMedium", "tokenized" } ), _ProductsId:Shape.TextBox( Id: "products-id", Name: "ProductsId", Title: T("products id"), Description: T("The id to provide on the products div element."), Classes: new[] { "textMedium", "tokenized" } ), _ProductsClass:Shape.TextBox( Id: "products-classes", Name: "ProductsClasses", Title: T("products classes"), Description: T("The class to provide on the products div element."), Classes: new[] { "textMedium", "tokenized" } ) ) ); return f; }; context.Form("ProductsListLayout", form); } } }
第三步需要新建一个ProductsListLayout的类,此类必须实现Orchard里ILayoutProvider接口,此类的作用有两点,第一:为Projections(后面章节有说明)选择布局供时提供布名称和说明,第二:调用构造具体布局的实现,并提供class 和 id传参,具体代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using Orchard.ContentManagement; using Orchard.DisplayManagement; using Orchard.Localization; using Orchard.Projections.Descriptors.Layout; using Orchard.Projections.Models; using Orchard.Projections.Services; namespace UBOfficeTheme.Providers.Layouts { public class ProductsListLayout : ILayoutProvider { private readonly IContentManager _contentManager; protected dynamic Shape { get; set; } public Localizer T { get; set; } public ProductsListLayout(IShapeFactory shapeFactory, IContentManager contentManager) { _contentManager = contentManager; Shape = shapeFactory; T = NullLocalizer.Instance; } public void Describe(DescribeLayoutContext describe) { describe.For("Html", T("Html"), T("Html Layouts")) .Element("ProductsList", T("ProductsList"), T("Organizes content items in a ProductsList."), DisplayLayout, RenderLayout, "ProductsListLayout" ); } public dynamic RenderLayout(LayoutContext context, IEnumerable<LayoutComponentResult> layoutComponentResults) { string cultureId = context.State.CultureId; string cultureClasses = context.State.CultureClasses; string culturetitleClasses = context.State.CultureTitleClasses; string productsId = context.State.ProductsId; string productsClasses = context.State.ProductsClasses; IEnumerable<dynamic> shapes = context.LayoutRecord.Display == (int)LayoutRecord.Displays.Content ? layoutComponentResults.Select(x => _contentManager.BuildDisplay(x.ContentItem, context.LayoutRecord.DisplayType)) : layoutComponentResults.Select(x => x.Properties); return Shape.ProductsList( Id: cultureId, Items: shapes, pcultureClasses: new[] { cultureClasses }, pculturetitleClasses: new[] { culturetitleClasses }, productsClasses: new[] { productsClasses }, productsId : productsId ); } public Func<LayoutContext, LocalizedString> DisplayLayout { get; set; } } }
第四步新建一个LayoutShapes 此类必须实现Orchard里IDependency接口,此类的作用为本Theme解决方案里的所有自定义布局提供具体实现,定义布局方法是有三点需要注意,第一:在方法前必须要加上 [Shape]的属性,第二点:方法名称必须和ProductsListLayout类中的RenderLayout方法返回语句中Shape调用名称相同。第三点:定义的方法参数除去dynamic Display, TextWriter Output, HtmlHelper Html由Orchard本身框架提供,其余一部由ProductsListLayout类中的RenderLayout方法返回语句中的Shape调用方法传递,一部是自己定义给class使用的字典集合。具体代码如下:
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Web; using System.Web.Mvc; using Orchard; using Orchard.Autoroute.Models; using Orchard.ContentManagement; using Orchard.DisplayManagement; using Orchard.Localization; using Orchard.UB.Portal.Models; using Orchard.UI.Resources; namespace UBOfficeTheme.Providers.Layouts { public class LayoutShapes : IDependency { private readonly IWorkContextAccessor _workContextAccessor; private readonly IContentManager _contentManager; private readonly IOrchardServices _orchardServices; public Localizer T { get; set; } public LayoutShapes(IOrchardServices orchardServices, IWorkContextAccessor workContextAccessor, IContentManager contentManager) { _workContextAccessor = workContextAccessor; T = NullLocalizer.Instance; _contentManager = contentManager; _orchardServices = orchardServices; } [Shape] public void ProductsList(dynamic Display, TextWriter Output, HtmlHelper Html, string Id, IEnumerable<dynamic> Items, IEnumerable<string> pcultureClasses, IDictionary<string, string> pcultureAttr, IEnumerable<string> pculturetitleClasses, IDictionary<string, string> pculturetitleAttr, IEnumerable<string> productsClasses, IDictionary<string, string> productsAttr, string productsId) { if (Items == null) return; var items = Items.ToList(); var itemsCount = items.Count; if (itemsCount < 1) return; string baseUrl = _workContextAccessor.GetContext().CurrentSite.BaseUrl; var cultureDivTag = GetTagBuilder("div", Id, pcultureClasses, pcultureAttr); var cultureDivTitleTag = GetTagBuilder("div", string.Empty, pculturetitleClasses, pculturetitleAttr); var proddetailDivTag = GetTagBuilder("div", productsId, productsClasses, productsAttr); var proddetailUlTag = GetTagBuilder("ul", productsId, productsClasses, productsAttr); Output.Write(cultureDivTag.ToString(TagRenderMode.StartTag)); Output.Write(cultureDivTitleTag.ToString(TagRenderMode.StartTag)); Output.Write("<h6 class='fl'><strong></strong>新品推荐<span>NEW PRODUCTS</span></h6>"); Output.Write(string.Format("<a href='{0}/{1}' class='fr'></a>", baseUrl, "executivedesk")); Output.Write(cultureDivTitleTag.ToString(TagRenderMode.EndTag)); Output.Write("<div>"); Output.Write(proddetailDivTag.ToString(TagRenderMode.StartTag)); Output.Write("<a href="javascript:void(0)" id="prev" ></a>"); Output.Write("<div>"); Output.Write(proddetailUlTag.ToString(TagRenderMode.StartTag)); for (int i = 0; i < items.Count; i++) { var pictureField = ((Orchard.ContentManagement.ContentItem)items[i].ContentItem).Parts.SelectMany(p => p.Fields).Where(f => f.Name.Contains("picture")).SingleOrDefault(); var imglist = ((Orchard.MediaLibrary.Fields.MediaLibraryPickerField)pictureField).MediaParts.OrderBy(c => c.AlternateText).ToList(); foreach (var item in imglist) { Output.Write("<li><a href='{2}/{3}'><img src='{0}' alt='优比家具-{1}'/></a></li>", item.MediaUrl, item.Title, baseUrl,item.Caption); } } Output.Write(proddetailUlTag.ToString(TagRenderMode.EndTag)); Output.Write("</div>"); Output.Write("<a href="javascript:void(0)" id="next" ></a>"); Output.Write(proddetailDivTag.ToString(TagRenderMode.EndTag)); Output.Write(cultureDivTag.ToString(TagRenderMode.EndTag)); } static TagBuilder GetTagBuilder(string tagName, string id, IEnumerable<string> classes, IDictionary<string, string> attributes) { var tagBuilder = new TagBuilder(tagName); tagBuilder.MergeAttributes(attributes, false); foreach (var cssClass in classes ?? Enumerable.Empty<string>()) tagBuilder.AddCssClass(cssClass); if (!string.IsNullOrWhiteSpace(id)) tagBuilder.GenerateId(id); return tagBuilder; } } }
到这里代码编写全部完成。下一篇介绍配置。