翻譯自:
動態類型系統
Content item是Orchard中的原子, 好比blog post, pages, products, widgets
探索Content item原子
做爲開發者,咱們首先會想到Content item是一個類的實例(好比blog post類), 類中包含了property, method等. 實際的Content item不是由簡單類型的屬性等組成的, 而是由content part組成,這是Orchard中的重要概念.
一個blog post典型的由URL, title, date, rich text body, tags和comments這些parts組成. 可是這些parts不僅是爲blog post所用. 好比tag, rich text body也會被用在page上.
另外, CMS中的content type並非一成不變的. Blog post之前是simple text, 可是很快就發展的更加複雜. 能夠包含videos, podcasts或者image galleries. 甚至, 若是你在旅行, 你會在blog post中提供位置服務.
Content parts是解決變化的key, 須要位置服務, 那麼只須要給blog post content item添加上mapping part. 這些不能有developer來作,而是admin來. 因此, 擴展不能是依賴於.net的一些類型的, 而是metadata-driven, 在運行時建立, 可以經過後臺頁面管理. 下圖就是Orchard的content type編輯頁.
Orchard擴展方法:
使用後臺的Orchard Content type editor
界面能作的事情,也能夠經過代碼實現, 以下:
item.Weld(part);
上面的代碼是在content item中動態的添加了part. 但這只是給instance添加了part, 若是咱們想給該Content type都添加上part, 能夠這樣:
ContentDefinitionManager.AlterTypeDefinition(
"BlogPost", ctb => ctb.WithPart("MapPart")
);
上面的方式也是實際上blog post content type的構建方式:
ContentDefinitionManager.AlterTypeDefinition("BlogPost",
cfg => cfg
.WithPart("BlogPostPart")
.WithPart("CommonPart", p => p
.WithSetting("CommonTypePartSettings.ShowCreatedUtcEditor", "true"))
.WithPart("PublishLaterPart")
.WithPart("RoutePart")
.WithPart("BodyPart")
);
你可能注意到blog post content type中沒有包含tags和comments. 的確是這樣, orchard系統是經過其它方式來實現添加tags和comments的.
點菜
site有個初始化的xml文件, 在site setup的時候, 會根據這個配置來初始化content type, 下面是blog post的配置.
<BlogPost ContentTypeSettings.Draftable="True" TypeIndexing.Included="true">
<CommentsPart />
<TagsPart />
<LocalizationPart />
</BlogPost>
建立Part
這是一個爲添加keyword和description的part, 能夠用來提升SEO友好. 具體使用的樣子:
在上面填寫的內容會生成到頁面上:
<meta content="Orchard is an open source Web CMS built on ASP.NET MVC." name="description" />
<meta content="Orchard, CMS, Open source" name="keywords" />
第一步是要解決如何存儲這些信息到數據庫. 嚴格的說, 並非全部的part都須要record, 由於不是全部的part的數據都存在數據庫中.
下面是咱們用來存儲keywords和description的MetaRecord:
public class MetaRecord : ContentPartRecord {
public virtual string Keywords { get; set; }
public virtual string Description { get; set; }
}
繼承自ContentPartRecord不是必須的, 可是繼承的話比較方便. 這個類有2個屬性, 都是virtual的, 這是爲了讓orchard在運行的時候可以方便的生成proxy.
添加MetaHandler來實現數據庫存儲.
public class MetaHandler : ContentHandler {
public MetaHandler(IRepository<MetaRecord> repository) {
Filters.Add(StorageFilter.For(repository));
}
}
對於數據庫的修改migration
public class MetaMigrations : DataMigrationImpl {
public int Create() {
SchemaBuilder.CreateTable("MetaRecord",
table => table
.ContentPartRecord()
.Column("Keywords", DbType.String)
.Column("Description", DbType.String)
);
ContentDefinitionManager.AlterPartDefinition(
"MetaPart", cfg => cfg.Attachable());
return 1;
}
}
上面的代碼, 第一部分是建立表, 而且和record匹配.
第二部分是說明任何content type均可以從Admin UI上attach MetaPart
The Part Class
實際的Part是另一個類, 繼承自ContentPart
public class MetaPart : ContentPart<MetaRecord> {
public string Keywords {
get { return Record.Keywords; }
set { Record.Keywords = value; }
}
public string Description {
get { return Record.Description; }
set { Record.Description = value; }
}
}
這裏的這個Part作成了MetaRecord的代理類. 若是沒有代理MetaRecord的屬性, 也能夠經過父類ContentPart的Record屬性訪問到.
任何使用了MetePart的Content item, 都可以方便的訪問到Keywords和Description屬性
var metaKeywords = item.As<MetaPart>().Keywords;
Parts也應該是你具體實現一些behavior的地方, 好比, 一個組裝機器的part, 能夠添加方法或者property來列舉它的部件或者計算總價.
至於Part添加的這些behavior如何和用戶交互,這就是drivers要作的事情.
The Driver
Content item中的part都有機會參與到request的生命週期中和在asp.net MVC controller起做用. 可是隻能在request的部分生命週期中起做用, 而不是所有.
Content part driver扮演這個角色, 它不是一個完整意義上的controller, 沒有route匹配到它的方法.
它是爲一些事先定義好的events寫響應的方法, 好比Display, Editor.
Driver繼承自ContentPartDriver.
protected override DriverResult Display(MetaPart part, string displayType, dynamic shapeHelper)
{
var resourceManager = _wca.GetContext().Resolve<IResourceManager>();
if (!String.IsNullOrWhiteSpace(part.Description)) {
resourceManager.SetMeta(new MetaEntry {
Name = "description",
Content = part.Description
});
}
if (!String.IsNullOrWhiteSpace(part.Keywords)) {
resourceManager.SetMeta(new MetaEntry {
Name = "keywords",
Content = part.Keywords
});
}
return null;
}
這個driver實際上不是典型的, 由於大部分的drivers result是rendering, 這裏要把meta part呈現到head的meta標籤中.
HTML的head section是shared resource, 因此有一些特殊處理.
Orchard提供了API訪問這些shared resource. 這裏我使用了resource manager來設置metatags. resource manager會呈現實際的tags.
之因此這個方法返回null是由於它不須要呈現任何東西到頁面上. 通常driver的方法須要返回一個稱作shape的dynamic type, 相似於asp.net mvc中的 view model.
它是一個很是flexible的對象, 你可以接上任何東西, 匹配對應的template來呈現它, 並不須要建立一個特定的view model類
protected override DriverResult Editor(MetaPart part, dynamic shapeHelper) {
return ContentShape("Parts_Meta_Edit",
() => shapeHelper.EditorTemplate(
TemplateName: "Parts/Meta",
Model: part,
Prefix: Prefix));
}
Editor方法是用來呈現改part editor UI的. 比較典型的是返回一個特殊的shape對象用來, 組合成edition UI.
driver中的最後一個方法是處理從editor發送回來的post請求:
protected override DriverResult Editor(MetaPart part, IUpdateModel updater, dynamic shapeHelper) {
updater.TryUpdateModel(part, Prefix, null, null);
return Editor(part, shapeHelper);
}
這個方法中的代碼調用了TryUpdateModel自動地更新part數據. 當更新完成, 再調用Editor方法來返回一樣的editor shape.
Rendering Shapes
經過調用全部的parts的drivers, Orchard可以建立一個shapes tree, 在整個request中建立一個龐大的動態的view model.
下一個任務是搞清楚如何經過templates來呈現這些shapes. 它是經過每一個shape的名字(若是是editor方法, 就是Parts_Meta_Edit)在整個系統定義範圍內尋找模板文件, 好比當前的theme和module的views文件夾.
這是很是重要的擴展點, 由於它容許你可以重寫系統的呈現, 只須要你在你的theme中放入正確name的template 文件便可.
在Views\EditorTemplates\Parts下的個人module文件夾下, 我寫了一個Meta.cshtml文件
@using Vandelay.Industries.Models
@model MetaPart
<fieldset>
<legend>SEO Meta Data</legend>
<div class="editor-label">
@Html.LabelFor(model => model.Keywords)
</div>
<div class="editor-field">
@Html.TextBoxFor(model => model.Keywords, new { @class = "large text" })
@Html.ValidationMessageFor(model => model.Keywords)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Description)
</div>
<div class="editor-field">
@Html.TextAreaFor(model => model.Description)
@Html.ValidationMessageFor(model => model.Description)
</div>
</fieldset>
全部的都是content
在我講到其它擴展的時候, 我想提醒一旦你懂了content item type系統, 你就懂得了Orchard中最重要的部分. 不少系統中重要的部分都是定義成了content item.
好比, a user是content item, 這樣咱們可以方便的添加properties到profile modules中. 咱們也有widget content item, 可以呈現到theme定義的zones中. 這是orchard中如何顯示search form, blog archives, tag clouds和其它的sidebar UI的方式.
但最令人意外應該是site自己也使用了content items. orchard中的 site settings也是content items, 這也是orchard的優點所在. 當你想添加本身的site settings的時候, 你所要作的就只是再添加一個新的part到site content type中, 而後添加一個admin edition UI. 一個統一擴展的content type系統是一個很是好的概念.
打包Extensions
Extensions都是以modules的方法分發的, 和實現相關的是以themes分發.
典型的Theme是一堆圖片, stylessheets和templates, 打包放到Themes文件夾下. 通常也有一個theme.txt文件來定義一些metadata信息.
相似的, module是在Modules文件夾下. 它也是一個asp.net mvc area, 包含了一些配置. 好比, 它須要一個module.txt來講明module的metadata。
做爲一個大網站的area, module須要可以處理好和一些share resources的關係. 好比, routes必須定義在一個繼承IRouteProvider的類中. Orchard會取全部modules的route來構建整個網站的route. 相似地, modules可以實現INavigationProvider來建立新的admin menu.
有意思的是, module的代碼一般不是以編譯好的二進制發佈(雖然這是可行的). 這是一個有意的安排, 咱們鼓勵對於module的修改, 當你從gallery下載一個module的時候,你能夠動手修改來知足你的需求.
能夠本身動手修改代碼是一些PHP CMS的強大之處, 好比Drupal, wordpress, 因此咱們也想在Orchard中提供這個. 當你下載一個module的時候,你下載了它的源代碼, 而後動態編譯. 當你改動了代碼, 改動立馬生效,而後module會從新編譯.
依賴注入
目前,咱們都在關注type system擴展, 由於這是orchard modules的主要功能, 可是orchard有不少其它的擴展點. 我想說一些使用這個框架的重要的原則.
一個重要的是在這個鬆散的module系統中作正確的事情. 在Orchard中, 機會全部低級別至上的都是module. 甚至modules都是由某個module來管理的. 若是你想這些modules之間不互相耦合, 那麼你實現的時候,就要注意解耦合.
實現這個的方法是使用依賴注入. 當你須要使用一個class的時候, 你不要直接實例化它, 那樣會致使你直接依賴於那個class. 你須要建立一個接口來抽象那個class, 而後用這個接口對象做爲構造參數.
private readonly IRepository<ContentTagRecord> _contentTagRepository;
private readonly IContentManager _contentManager;
private readonly ICacheManager _cacheManager;
private readonly ISignals _signals;
public TagCloudService(
IRepository<ContentTagRecord> contentTagRepository,
IContentManager contentManager,
ICacheManager cacheManager,
ISignals signals)
_contentTagRepository = contentTagRepository;
_contentManager = contentManager;
_cacheManager = cacheManager;
_signals = signals;
}
經過上面的例子, 你依賴於接口,而不是class, 無論這個接口的實現如何, 你都不須要修改你的代碼.
具體使用這個接口的代碼中不須要具體指明如何實例化. 這樣就實現了控制反轉, 具體提供什麼實例則是有framework來決定的.
固然, 這不限於orchard定義的接口. 任何module都可以提供它們本身的擴展點, 只須要指明繼承自IDependency, 很是簡單.
一些其它的擴展點
這裏, 我只是揭示一下擴展點. Orchard中有不少的接口能夠用於擴展. 更加誇張的說, orchard什麼都不是, 只是一個能夠隨意擴展的engine. 系統中全部的東西都包裝成了擴展.
在結束本文以前, 我想說一下系統中一些很是有用的接口. 因爲沒有更大的篇幅來講的更深, 可是能夠指明一個方向, 你能夠深刻代碼中去看看這些接口的用法.
- IWorkContextAccessor 可讓你的代碼來訪問當前request的work context. work context提供了HttpContext, current layout, site configuration, user, theme和culture信息. 它也提供了方法取得某個接口的具體實現.
- IContentManager提供了你要查詢和管理content items的全部東西
- IRepository<T> 提供了方法訪問更低級別的數據, 若是IContentManager不夠用, 能夠用這個.
- IShapeTableProvider 是用來提供shape的. 你能夠再shapes中攔截事件, 能夠修改shapes, 添加members, 在layout上移動shapes.
- IBackgroundTask, IScheduledTask and IScheduledTaskHandler 是一些接口來實現諸如在後臺執行一些時間長,或者按期執行的任務的
- IPermissionsProvider 運行你本身的module來定義你module中的權限.