- CMS
- 后端
- Web框架
【免费下载链接】OrchardCore
Orchard Core is an open-source modular and multi-tenant application framework built with ASP.NET Core, and a content management system (CMS) built on top of that framework.
Content Definitions(内容定义)是 Orchard Core 多租户架构下描述「Content Types(内容类型)、Content Parts(内容部件)、Content Fields(内容字段)」的元数据记录,它决定了一个租户里可以创建什么样的内容、每个内容由哪些部件和字段构成。本指南围绕 src/docs/guides/content-definitions/README.md 的核心脉络展开,先讲清默认的数据库存储与可选的File Content Definition文件存储两种模式各自的适用场景,再手把手演示如何通过 Deployment Plan(部署计划)把ContentDefinition.json文件中的定义平滑迁移回数据库,最后结合源码揭示底层存储契约与部署步骤的实现原理。读完本文,你将掌握内容定义存储的切换思路、完整迁移流程,并能理解背后的IContentDefinitionStore抽象与部署步骤机制。
一、什么是 Content Definitions
在 Orchard Core 中,Content Definitions是某个租户(tenant)所使用的Content Types、Content Parts与Content Fields的完整记录。它们是内容建模的「蓝图」:例如你在后台把Article类型挂上TitlePart、HtmlBodyPart,添加TextField等操作,最终都会落成一组结构化定义,持久化到租户对应的存储中。
- Content Type Definition:定义一个内容类型(如
Article、Page),包括它挂载了哪些部件(Parts)与字段(Fields),以及可用的编辑器、显示设置等。 - Content Part Definition:定义可复用的部件(如
TitlePart),部件可以被多个内容类型引用。 - Content Field Definition:定义字段(如
TextField、NumericField),字段通常附加在部件或内容类型之上。
从数据结构看,这些定义被统一收纳进一个ContentDefinitionRecord文档对象中,其中包含ContentTypeDefinitionRecords与ContentPartDefinitionRecords两个列表,参见 ContentDefinitionRecord.cs:
[FileDocumentStore(FileName = "ContentDefinition")] public class ContentDefinitionRecord : Document { public IList<ContentTypeDefinitionRecord> ContentTypeDefinitionRecords { get; set; } public IList<ContentPartDefinitionRecord> ContentPartDefinitionRecords { get; set; } }注意这个类上标注的[FileDocumentStore(FileName = "ContentDefinition")]特性——这正是文件存储模式下文件名的由来(文件名前缀ContentDefinition,序列化后即ContentDefinition.json)。
二、两种存储方式:数据库存储与文件存储
1. 默认方式:数据库存储(Database Content Definition Store)
默认情况下,Content Definitions 存储在数据库(YesSql 文档表)中。这是生产环境的推荐做法:
- 定义与站点数据一起落在数据库,天然支持事务与备份体系;
- 多租户场景下,每个租户的定义独立存储、互不干扰;
- 运行时通过内存缓存加速读取,避免频繁访问数据库。
其实现类为 DatabaseContentDefinitionStore.cs,它通过IDocumentManager<ContentDefinitionRecord>完成文档的读取与更新:
public class DatabaseContentDefinitionStore : IContentDefinitionStore { private readonly IDocumentManager<ContentDefinitionRecord> _documentManager; public Task<ContentDefinitionRecord> LoadContentDefinitionAsync() => _documentManager.GetOrCreateMutableAsync(); public Task<ContentDefinitionRecord> GetContentDefinitionAsync() => _documentManager.GetOrCreateImmutableAsync(); public Task SaveContentDefinitionAsync(ContentDefinitionRecord contentDefinitionRecord) => _documentManager.UpdateAsync(contentDefinitionRecord); }2. 可选方式:文件存储(File Content Definition Feature)
当启用名为File Content Definition的功能特性后,Content Definitions 不再写入数据库,而是保存在每个租户App_Data目录根部的ContentDefinition.json文件中。以默认租户为例,路径为:
App_Data/Sites/Default/ContentDefinition.json其实现类为 FileContentDefinitionStore.cs,唯一区别在于它使用的文档管理器绑定的是文件文档存储:
public class FileContentDefinitionStore : IContentDefinitionStore { private readonly IDocumentManager<IFileDocumentStore, ContentDefinitionRecord> _documentManager; public Task<ContentDefinitionRecord> LoadContentDefinitionAsync() => _documentManager.GetOrCreateMutableAsync(); public Task<ContentDefinitionRecord> GetContentDefinitionAsync() => _documentManager.GetOrCreateImmutableAsync(); public Task SaveContentDefinitionAsync(ContentDefinitionRecord contentDefinitionRecord) => _documentManager.UpdateAsync(contentDefinitionRecord); }从源码可以清晰地看到:文件存储与数据库存储只是「落盘介质」不同,对外暴露的接口完全一致——两者都实现了IContentDefinitionStore接口。该接口在 IContentDefinitionStore.cs 中定义了三个方法:
LoadContentDefinitionAsync():加载用于更新的可变定义(不应缓存);GetContentDefinitionAsync():从缓存获取用于共享的不可变定义;SaveContentDefinitionAsync(record):更新存储并刷新缓存。
而切换两种存储的关键,在于依赖注入时注册哪个实现。参见 ServiceCollectionExtensions.cs 中的AddFileContentDefinitionStore:
public static IServiceCollection AddFileContentDefinitionStore(this IServiceCollection services) { services.RemoveAll<IContentDefinitionStore>(); services.AddScoped<IContentDefinitionStore, FileContentDefinitionStore>(); return services; }该方法先移除默认的数据库实现,再注册文件实现。它由 OrchardCore.Contents 模块的 Startup.cs 中带[Feature("OrchardCore.Contents.FileContentDefinition")]特性的FileContentDefinitionStartup在ConfigureServices阶段调用:
[Feature("OrchardCore.Contents.FileContentDefinition")] public sealed class FileContentDefinitionStartup : StartupBase { public override void ConfigureServices(IServiceCollection services) { services.AddFileContentDefinitionStore(); } }这就是「启用/禁用功能特性即切换存储」的底层原理:功能启用时注册文件实现,功能禁用时回归默认的数据库实现。
3. 两种存储的定位差异
| 对比维度 | 数据库存储(默认) | 文件存储(File Content Definition) |
|---|---|---|
| 数据落盘位置 | 租户数据库(YesSql 文档表) | App_Data/Sites/<租户名>/ContentDefinition.json |
| 适用阶段 | 生产(Production) | 开发(Development) |
| 主要优势 | 与站点数据统一管理,适合线上运行 | 文件可进版本控制,便于代码评审与 diff 对比 |
| 切换方式 | 禁用File Content Definition特性即回退 | 启用OrchardCore.Contents.FileContentDefinition特性 |
官方文档给出的定位非常明确:文件存储模式在项目的开发(Development)阶段非常有用——你可以把ContentDefinition.json纳入 Git 等版本控制系统,团队成员改动内容建模后能直观地看到 diff,评审更轻松;当站点进入**生产(Production)**阶段,则建议禁用该特性,把 Content Definitions 存回数据库,让定义与线上数据保持一致并由数据库统一管理。
三、为什么开发阶段推荐文件存储
内容定义的每一次改动(新增类型、挂载部件、调整字段设置)都会改写这组元数据。如果全部存进数据库:
- 改动不可见,无法通过代码评审逐行审查「谁改了什么」;
- 难以把一套精心设计的内容模型随代码一起分发给其他环境。
而文件存储让内容模型变得「像代码一样可评审、可复用」:
ContentDefinition.json是纯文本 JSON,可以直接放进版本库;- 拉取代码后,新的开发环境启动即可获得与团队一致的内容模型;
- 冲突、回滚、分支合并都能用常规 Git 流程处理。
需要注意的是,文件存储在运行期同样会被内存缓存兜底(ContentDefinitionManager维护类型/部件定义的缓存字典),频繁读取不会直接打文件系统;同时,ContentDefinitionManager通过LoadContentDefinitionAsync(可变)与GetContentDefinitionAsync(不可变)两条路径区分「编辑中」与「只读共享」两种使用场景,参见 ContentDefinitionManager.cs。
四、迁移实战:把 ContentDefinition.json 迁回数据库
当你完成开发、准备把站点推向生产时,需要把ContentDefinition.json中的定义迁移进数据库。官方推荐使用Deployment Plan(部署计划)作为迁移载体,整个过程分三步,下面逐一展开。
Step One:创建部署计划并导出定义
在管理后台执行如下操作:
- 进入Tools -> Deployments -> Plans菜单;
- 点击Add Deployment Plan,新建一个部署计划;
- 将部署计划命名为
Content Definitions(建议保持这个名称,便于识别); - 选中
Content Definitions部署计划; - 点击Add Step(添加步骤);
- 选择Update Content Definitions(更新内容定义)步骤;
- 勾选Include all content types and parts definitions.(包含全部内容类型与部件定义)复选框;
- 执行该部署计划,并在执行时选择File Download Target(文件下载目标)。
执行完成后,浏览器会下载一个名为ContentDefinitions.zip的文件到本地。这个压缩包内即包含了当前文件存储里的全部内容定义(快照)。
这一步背后的部署步骤实现是 ContentDefinitionDeploymentStep.cs。从源码可以看到它的两个关键配置项:
public class ContentDefinitionDeploymentStep : DeploymentStep { public bool IncludeAll { get; set; } // 是否包含全部内容类型与部件 public string[] ContentTypes { get; set; } // 指定的内容类型列表 public string[] ContentParts { get; set; } // 指定的内容部件列表 }- 勾选「包含全部内容类型和部件定义」对应
IncludeAll = true,导出时不做筛选; - 若不勾选,则可通过
ContentTypes/ContentParts两个数组有选择地导出部分定义; - 该步骤的
Name为ContentDefinition,界面上显示的中文标题Update Content Definitions由IStringLocalizer提供(源码中Title = S["Update Content Definitions"],分类归入Content Management)。
Step Two:禁用 File Content Definition 特性
在把定义导入数据库之前,需要先让系统「回到数据库存储」:
- 进入Tools -> Features菜单;
- 找到名为
File Content Definition的功能特性; - 禁用该特性。
如前文所述,禁用该特性后,FileContentDefinitionStartup不再注册,依赖注入容器中的IContentDefinitionStore恢复为默认的DatabaseContentDefinitionStore实现(参见 ServiceCollectionExtensions.cs 与 Startup.cs)。
提示:此时站点处于「定义已从文件移除、尚未写入数据库」的过渡窗口,建议在低峰期操作,并确认已导出
ContentDefinitions.zip且文件完好。
Step Three:导入部署包
最后把第一步导出的文件导回系统:
- 进入Tools -> Deployments -> Package Import菜单;
- 选择第一步下载的
ContentDefinitions.zip文件; - 点击Import(导入)。
导入过程会执行部署计划中的Update Content Definitions步骤,把IncludeAll覆盖的全部内容类型与部件定义写回当前存储——此时当前存储已是数据库,因此定义便成功落入了数据库。
迁移完成后的校验
导入完成后建议做如下检查:
- 在后台Content -> Content Types中确认各内容类型及其部件、字段完整无缺;
- 新建一个示例内容项,验证字段编辑、预览等行为与迁移前一致;
- 确认
App_Data/Sites/Default/下不再生成或更新ContentDefinition.json(特性已禁用)。
五、迁移原理小结:为什么部署计划能胜任这件事
整个迁移流程之所以成立,是因为 Orchard Core 的部署体系本身就是「跨环境搬运配置」的标准通道:
- 导出阶段:部署计划的
ContentDefinitionDeploymentStep从当前存储(文件)读取定义,打包成ContentDefinitions.zip; - 切换阶段:禁用特性后,
IContentDefinitionStore实现从FileContentDefinitionStore切换为DatabaseContentDefinitionStore; - 导入阶段:Package Import 执行部署步骤,将定义写入新的存储(数据库),完成介质迁移。
由于FileContentDefinitionStore与DatabaseContentDefinitionStore都实现同一个 IContentDefinitionStore 契约(Load / Get / Save 三方法),导入逻辑无需感知底层介质差异,这也是「文件→数据库」可以无缝迁移的根本原因。
六、总结
- Content Definitions是租户级的内容建模元数据,包含类型、部件与字段三类定义,统一存放在
ContentDefinitionRecord文档中; - 默认存数据库;启用
File Content Definition特性后改存App_Data/Sites/<租户>/ContentDefinition.json,开发阶段便于版本控制与评审,生产阶段建议存数据库; - 两种介质共享
IContentDefinitionStore抽象,切换本质是依赖注入实现的替换(AddFileContentDefinitionStore); - 迁移三部曲:创建部署计划导出
ContentDefinitions.zip→ 禁用File Content Definition特性 → 通过 Package Import 导入部署包; - 部署步骤 ContentDefinitionDeploymentStep 支持
IncludeAll全量导出或按ContentTypes/ContentParts选择性导出,迁移时按需勾选即可。
掌握了这套流程,你就能在「开发期用文件管理内容模型、上线前平滑切回数据库」之间自由切换,让内容建模既保持代码级可评审性,又具备生产环境的稳定性。
- CMS
- 后端
- Web框架
【免费下载链接】OrchardCore
Orchard Core is an open-source modular and multi-tenant application framework built with ASP.NET Core, and a content management system (CMS) built on top of that framework.
相关推荐
Ahoy 数据存储架构详解:从数据库到自定义存储的完整迁移方案
Ahoy 数据存储架构详解:从数据库到自定义存储的完整迁移方案 Ahoy 是 Rails 生态中简单而强大的第一方分析解决方案,其数据存储架构设计巧妙且高度可扩
深入理解 agno 自定义学习存储(Custom Learning Store):从内存实现到数据库持久化的完整实战指南
深入理解 agno 自定义学习存储(Custom Learning Store):从内存实现到数据库持久化的完整实战指南 导读 在 agno 的 Agent 2
人工智能大模型AI AgentAgent 框架多智能体工具调用RAGAgent 工作流Agent 记忆Django Simple Captcha国际化指南:多语言支持与本地化配置
Django Simple Captcha国际化指南:多语言支持与本地化配置 Django Simple Captcha是一个极其简单但高度可定制的Django
后端应用安全
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考