概述
本文翻译仅供学习之用,了解Orchard工作原理设计思想、技术点及关键词,如有缺漏请不吝指正。鉴于能力有限定有诸多曲解或不完整的地方,请海涵。不定时完善整理。
CMS不像常规的web程序,它更像一个程序容器。 设计系统时采用一个开放类型的架构,扩展作为首要特性是必需的。
架构图:
基础:
主要使用以下现有的框架和类库
- ASP.NET MVC: web开发框架。 MVC 关注分离
- NHibernate: ORM工具. 将Orchard 内容持久化 、通过移除模块开发的持久化关注极大的简化了数据模型
- Autofac: IoC container 依赖注入容器 . 大量使用DI (dependency injection 依赖注入 ) 。实现简单,而且范围生命周期都由Orchard Framework进行管理 .( IAuthorizationService, RolesBasedAuthorizationService and XmlRpcHandler.)
- Castle Dynamic Proxy: 动态代理.
Orchard Framework
Orchard Framework是建立在这些基础上的一个额外的抽象层。也是最深的层。它包含了程序引擎 还有不能被孤立成模块的部分,他们就是基础模块依赖的--也可以成为基础类库。
启动Orchard
web application启动后,Orchard Host被创建,在应用程序域级别是个单例。
the hos获得使用ShellContextFactory的当前租户(current tenant)的Shell。租户:程序的实例,该程序按照用户隔离,运行在同一应用程序域中, 提高了站点密度(其实是一个逻辑站点的意思。Orchard支持在一个物理的站点上建立多个逻辑的站点。每个逻辑站点都是独立,有自己的数据,自己的主题等) 。
Shell是Tenant级别的单例,并且代表了该Tenant。他可以有效提供tenant-level的隔离同时对多租户保持模块 编程模型无关的。
shell一旦被创建就可以获得ExtensionManager可用扩展列表。扩展包括模块和主题.默认实现是扫描 模块主题的扩展目录。
同时从ShellSettingsManager获得该用户的设置列表。默认实现从App_Data子文件夹中获取,但是也可以从其他地方获得。
shell获得CompositionStrategy对象,并用它为当前host的扩展列表和当前租户的设置列表准备IoC容器。它返回的结果ShellBlueprint,不是shell的IoC容器,他是依赖、控制器、记录蓝图的列表。
ShellSetting列表(每个租户) 和ShellBluePrint 被扔到ShellContainerFactory.CreateContainer 获得ILifetimeScope--主要使IoC容器界定范围在租户级别以便模块依赖注入时也被指定到当前租户不需要任何特别配置。
Dependency Injection 依赖注入
Orchard中,创建可注入的依赖关系的标准方法:创建一个接口继承IDependency或其派生接口,然后实现该接口。在
消费方,在构造函数采用该接口类型的参数。framework将会发现所有依赖关系、负责实例化并按需注入实例。中
三种不同的依赖关系范围,通过继承相应的接口:
•Request: 每次HTTP请求都为创建依赖实例 请求一旦被处理就会将其销毁。继承IDependency。 reasonably cheap to create
•Object: 一个对象对接口产生依赖性就会每次创建一个新的实例。实例不会共享。 继承ITransientDependency。 extremely cheap to create.
•Shell: 每个shell/tenant有且只有一个实例。 继承ISingletonDependency. 需要维持shell生命周期的状态 。
替换现有的依赖
通过OrchardSuppressDependency特性修饰类进行现有依赖的替换--使用完全限定的类型名称作为参数进行替换。
依赖排序
一些依赖不是唯一的 而是列表的一部分。一些依赖不是唯一的 而是列表的一部分。通过修改模块的清单中 feature 的Priority属性。
Features:
Orchard.Widgets.PageLayerHinting:
Name: Page Layer Hinting
Description: ...
Dependencies: Orchard.Widgets
Category: Widget
Priority: -1
ASP.NET MVC
Orchard建立在ASP.NET MVC,但为了添加如主题、租户隔离等功能,引入了一个额外的中间层 用于呈现mvc端 的期望内容 并且在Orchard端 按照Orchard内容级别上分割事情。
请求特定视图时,LayoutAwareViewEngine开始启动。 严格来说 ,这个不是一个新的视图引擎,他不关注真正的呈现,它通过查找依赖于当前主题的正确视图,并将 呈现工作委托真正的视图引擎。
相似的, route providers, model binders and controller factories 作为 单一入口点而且将请求分派到恰当底层对象。
对于routes,我们有 多个 providers of routes(一般来自 modules 层)和 one route publisher .model binders controller factories 也是如此。
Content Type System 内容类型系统
Orchard中为了提供必要的灵活性,内容通过实际的类型系统进行管理,该系统在某种程度上比.NET基础类型更加丰富、动态化:类型在运行时动态组成并反射内容管理的关注点.
Types, Parts, and Fields
任意内容类型,包含站点管理员通过code-free方式创建的动态内容.这些内容有 具体处理一个特定的问题分内容聚合而成。原因是很多关注点涉及很多类容类型。
例如:blog列表、产品、一段视频都会有路由地址、注释和标签。因此这些在Orchard中会被按照单独的内容部分进行处理。 注释管理模块按照这种方式可以适用于任意内容类型,包括不明所以然的东东。
Parts 包含properties 、 content fields。Content fields 可以同样的方式复用。特定的字段类型将会被 系统部分或者内容类型 代表性的使用。
parts fields 的区别:操作的规模 以及语义。
Fields比parts更颗粒度更细。
例如 field 类型描述手机号码或者坐标,然而 part类型描述的是一个整体(注释(动作 包含lifetime lifecyle) 标记).
最重要的区别在于语义: part = "is a" 关系 field ="has a" 关系。
例如, T恤是一个产品 它有SKU和价格 属性。你不会说T恤是个产品 T恤是个价格
T恤是个SKU.
"Shirt" content type 由 Product part组成。Product part由 称作price 的Money field 和称作 SKU 的String field组成。
其他的区别 你只有有且一部分 的给定类型/内容类型,让你认为这是 is-a 关系
因此 part 有很多给定类型的fields.换而言之, part中fields 相当于field's t类型的字典。content type就是part类型的列表(不包含名字)。
如果你想content type有多个实例选择field类型。
Content类型剖析
content 类型 通过 content parts 类型构建. 代码级别,Content parts通常和如下相关的:
- Record(记录), part数据的POCO持久化.
- model class 继承并实现ContentPart<T> T record type
- repository(数据仓库) 不需要有模块实现 负责数据访问的对象
handlers(处理器). 实现IContentHandler接口 是一组事件处理器 是事件像 OnCreated 或 OnSaved.
基本上在content item的生命周期内使用钩子执行大量的任务.也参与content items构造函数的构建。基于ContentHandler的 Filters collection可以让handler添加对content type添加常见的行为。
例如: StorageFilter 更加方便的声明 content part的持久化如何处理,只需执行代码
Filters.Add(StorageFilter.For(myPartRepository)); Orchard系统将会myPartRepository数据持久化到数据库中。
ActivatingFilter负责 多个 parts 合成一个类型
调用方法Filters.Add(new ActivatingFilter<BodyAspect>(BlogPostDriver.ContentType.Name));
添加body content part到博客帖子drivers(驱动器). 更友好更专业的 handlers处理程序。 继承ContentPartDriver<T> T a content part type。
另一方面,Handlers是没有指定类型的content part type。 Drivers 可以看作特定part的controller。 他通过theme引擎呈现形状。
内容管理
ContentManager对象可以访问所有的内容。它有对应的方法查询content store,用于内容版本化和管理发布状态
事务
每个HTTP请求都会自动创建事务.请求中所有的操作都是环境事务(ambient transaction)的一部分
如果请求的代码终止了事务,所有的操作将会回滚。事务不能明确的终止,那么所有操作将会提交没有明确的结果。
Request生命周期
我们以请求一篇博客文章的示例:
一篇博客文章的请求进来后,程序首先查找各个模块可用的路由 然后查找blog模块匹配路由。路由为请求指定了博客文章的controller action ,从content manager查找文章。
action基于请求主对像 在content manager调用BuildDisplay获得Page Object Model (POM)。这样文章就从content manager取回了。
博客文章有自己的controller,但并不适合所有的content types.核心路由部分的通用ItemController 会处理动态content types。ItemController的Display action 和 blog post controller一样:通过标头从content manager获取内容,然后根据结果构建POM.
依赖布局视图引擎 确定的正确的视图根据当前主题和在视图命名上 model's type连同Orchard conventions 。
视图内,会生成更多的动态Shape例如区域定义。
通过主题引擎按照出现的顺序递归生成最终呈现内容,通过寻找正确的模板或者Shape方法来呈现每个在POM的Shape。
Widgets
一种内容类型,包含Widget content part和widget stereotype(铅版)。同样由parts 和 fields组成,这样使用其他内容类型的编辑呈现逻辑进行编辑。共享行为模块(Building Blocks), 任何现存的content part都会被widget复用,这样开销几乎为0.
通过widget层,添加widget到页面。该层是widget的集合。它有名字和规则,决定哪些页面将会展示,widgets列表、相关区域布置、排序、设置。
规则 通过IronRuby表达式 附属于每层。这些表达式可以使用IRuleProvider的任何实现
Orchard直接提供了两个:url、authenticated
站点设置
站点是一个内容条目(content item),内容条目可以让模块扩展额外的parts。这也是模块可以配置站点设置的原理。
站点设置租户级别。
Event Bus 事件总线
Orchard系统和模块通过创建依赖关系接口、注入实现暴露扩展点。
通过实现的接口或者实现同样名字和方法的接口借入扩展点。 换句话说,Orchard不需要严格的强类型接口通信,使插件以扩展一个扩展点不依赖于程序集的定义。
当扩展点触发注入实现,事件总线就会发布一条消息。监听事件总线的对象就会将消息分派给 继承一个合适的接口的类方法。
Commands 命令
Orchard很多操作可以通过admin UI使用命令行进行。这些命令由实现 CommandName特性修饰的ICommandHandler接口的类方法暴露出来。
命令行工具在运行时通过模拟网站环境和使用反射检查组件发现可用的命令。该命令运行环境是尽可能接近实际运行的站点。
搜索和索引
搜索和索引的使用Lucene默认情况下实现的, 默认的实现可以被其他引擎所取代。
缓存
Orchard缓存依赖于ASP.NET缓存,但我们通过ICache接口暴露一个辅助的API,使用Get方法调用。如果缓存已经不包含所请求的实体,Get使用key和方法产生缓存实体值。
Orchard缓存API的主要优点是,它每个租户透明地工作。
文件系统
文件系统根据环境被抽象以便直接存储在物理文件系统上或者像Azure的云存储上。体模块是使用该抽象文件系统模块的一个例子。
用户和角色
用户是内容项(虽然不可路由),例如对于概要文件模块易于使用其他字段扩展。角色是content part 被焊接到users。
Permissions 权限
每个模块都可以公开一组权限,以及如何将这些权限默认情况下应授予默认角色。
任务
模块可以通过调用的实现IScheduledTaskManager接口的CreateTask的类安排任务。通过实现IScheduledTaskHandler执行任务。Process方法检查任务类型名称并决定是否处理它。
任务自ASP.NET线程池的一个单独的线程中运行。
Notifications 通知
模块可以通过继承INotifier并调用其方法之一 将管理界面展示消息。 获取上INotifier的依赖,并调用其方法之一面消息到管理界面。多个通知可以作为任何请求的一部分进行创建。
Localization 本地化
应用程序及其模块的本地化通过将字符资源包装后传入T方法:@T(“此字符串可以本地化”)。 Using the localization helpers
资源管理器从位于程序中的特定位置的PO文件加载本地资源字符串。
内容项目本地化通过不同的机制来实现:内容项目的本地化版本是物理上是由一个特殊的部分链接在一起的各个内容项。
culture管理器决定当前使用culture。默认实现返回已在站点设置中配置的culture,还可以从用户配置文件或从浏览器的设置得到它。
Logging 日志
通过继承ILogger的实现日志。不同的实现可将日志条目发送到不同的存储类型。 使用 Castle.Core.Logging进行记录的实现。
Orchard Core
Orchard.Core程序集包含了一组运行必需的模块。其他模块可以安全引用这些模块。
core modules:feeds, navigation 、routable.
Modules 模块
系统内置模块如 blogging or pages, 但第三方模块也容易创建。
模块仅仅是使用了扩展的manifest.txt文件ASP.NET MVC area。
一个模块通常包含事件处理程序,内容类型,其默认呈现模板以及一些管理界面。
每当csproj文件或csproj文件引用的文件有变化时, 模块可以动态编译。不需要开发人员甚至使用IDE进行编译。
模块必须放置在Modules文件夹(Orchard.Web/Modules/MyModule)和文件夹名称必须由项目产生的编译的DLL的名称相匹配。所以,如果你有一个名为My.Custom.Module.csproj自定义模块项目,并将其编译为My.Custom.Module.dll,模块根文件夹必须命名为My.Custom.Module。 [~/Modules/My.Custom.Module/]
Themes 主题
基本的设计原则,即所有它生成的HTML可以从主题更换,包括模块产生的标记。Conventions 约定确定文件对应主题的文件层级。
整个渲染机制是基于shapes。该主题引擎的工作是找到当前主题,因为主题确定呈现每个shape的最好方法。每个形状可以具有可由模块(views文件夹模板或在代码shape方法) 定义一个默认的呈现。
该默认渲染可以由当前主题重写。通过shape的模板版本或shape方法实现。
主题可以有父级,可以让子主题定制化 或在父级上进行修改。系统提供了名叫Theme Machine的基础主题作为父主题十分容易使用。
和模块一样,主题可以包含很多代码: 有自己的csproj文件,并从动态编译受益。主题定义方法,将设置暴露管理用户界面。
通过继承IThemeSelector 实现当前主题选择,它们返回一个主题名称和任何请求的优先级进行。这让很多选择器作出贡献的主题的选择。系统提供了IThemeSelector四种实现方式:
- SiteThemeSelector 选择当前配置的租户或网站具有低优先级的主题。
- AdminThemeSelector 只要当前URL是一个admin URL,接管并有返回高优先级返回admin theme。
- PreviewThemeSelector 如果当前用户是发起主题预览,使用预览主题重写站点当前主题
- SafeModeThemeSelector 应用程序是在“安全模式”该选择器才可使用是唯一的选择时可用的,通常在安装过程中发生。它具有非常低的优先级。
主题选择器的一个例子可能是一个促进当UA被识别为属于一个移动设备的移动的主题。
引用
参考:http://www.cnblogs.com/esshs/archive/2011/06/01/2067501.html
POCO参考:http://kb.cnblogs.com/page/89750/
环境事物 参考:http://www.cnblogs.com/artech/archive/2010/01/30/1660088.html
POM 参考:http://www.guru99.com/page-object-model-pom-page-factory-in-selenium-ultimate-guide.html
2016.4.17 v0.1