CMS后端Web框架【免费下载链接】OrchardCoreOrchard 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.项目地址https://gitcode.com/gh_mirrors/or/OrchardCore点击查看免费下载显示驱动程序Display Driver是 OrchardCore 内容呈现体系中承上启下的核心机制它负责把内容部件Part、内容字段Field或任意模型Model转换成可供 Razor/Liquid 模板渲染的 Shape并为每个 Shape 指定默认的摆放位置Location之后placement.json可以再对这些位置进行覆盖。本文以 OrchardCore 仓库为基准系统讲解四类驱动基类、三个需要重写的方法、Shape 构建与位置链式配置、表单回写与验证以及驱动在Startup.cs中的注册方式并结合仓库源码给出可直接复用的实战代码。一、什么是 Display Driver在 OrchardCore 的显示管线中Shape 是渲染的最小单元模板通过 Shape 类型被解析。驱动Driver承担模型 → Shape的桥接职责其工作方式如下系统在渲染内容项时向所有已注册的驱动广播构建显示/编辑事件驱动判断传入的 Part、Field 或 Model 是否是自己关心的类型若匹配驱动返回一个或多个IDisplayResult即 Shape 结果并为其附加默认位置位置的最终决定权交给placement.json——文档明确说明placement.json可以在之后覆盖驱动设定的默认位置。IDisplayResult是一个延迟执行的结果对象只有当 Shape 真正需要渲染时才会被创建详见下文Factory系列方法这是 OrchardCore 避免无谓对象分配的关键设计。二、四个基类与各自的构建上下文OrchardCore 针对不同的目标类型提供了四个抽象基类每个基类都对应特定的构建上下文Build Context。上下文携带当前渲染所需的元数据例如TypePartDefinition、PartFieldDefinition、Updater、HtmlFieldPrefix等。基类显示/编辑上下文用途ContentPartDisplayDriverTPartBuildPartDisplayContext/BuildPartEditorContext内容部件PartContentFieldDisplayDriverTFieldBuildFieldDisplayContext/BuildFieldEditorContext内容字段FieldSectionDisplayDriverTModel, TSectionBuildDisplayContext/BuildEditorContext设置区块站点设置、用户设置等TSection是实体上的一个属性DisplayDriverTModelBuildDisplayContext/BuildEditorContext任意其他模型2.1 Part 驱动与 Field 驱动的类型过滤从源码看两类驱动在进入构建前都会做类型守卫。ContentFieldDisplayDriverTField在 ContentFieldDisplayDriver.cs 中通过partFieldDefinition.FieldDefinition.Name与typeof(TField).Name比较来决定是否接管该字段而ContentPartDisplayDriverTPart在 ContentPartDisplayDriverTPart.cs 中把ContentPart强转为TPart转型失败即返回nullnull结果表示本驱动不处理该类型。2.2 Part/Field 驱动的 Shape 元数据增强Part 驱动重写了Factory方法在创建 Shape 后自动设置Differentiator与AlternatesPart 的 Shape 不同标识符为部件名例如HtmlBodyPart-Services、BagPart-LandingPage-ServicesField 的 Shape 不同标识符为部件名-字段名或部件名-字段名-Shape类型例如HtmlBodyPart-Description-TextField在Displaying事件中通过ContentPartAlternatesFactory/ContentFieldAlternatesFactory注入缓存好的候选模板列表alternates这正是后续模板匹配机制的基础。SectionDisplayDriverTModel, TSection的签名要求TSection : new()、TModel : class, IEntity其默认实现返回NullShapeResult()即null见 SectionDisplayDriver.cs。它适用于ISite、用户等实体对象上的设置区块——例如一个社交媒体设置区块。三、需要重写的方法无论选择哪个基类核心都是三个方法方法触发时机返回值Display(model, context)前端展示 / 后台管理展示IDisplayResultEdit(model, context)渲染编辑器表单页面IDisplayResultUpdateAsync(model, context)POST 提交——绑定与校验IDisplayResult通常直接返回Edit(...)以便回显三个方法都有对应的异步版本DisplayAsync、EditAsync。基类中同步版本的默认实现会自动包装为Task.FromResult(...)因此你既可以只重写同步版本也可以直接重写异步版本UpdateAsync本身始终是异步的表单绑定与校验天然适合async/await。从 ContentPartDisplayDriverTPart.cs 可以看到基类的默认实现DisplayAsync内部调用DisplayEditAsync内部调用EditUpdateAsync默认返回null不处理回写。同时驱动接口的BuildDisplayAsync/BuildEditorAsync/UpdateEditorAsync显式实现负责强转类型 → 设置Prefix→ 构造细分上下文 → 调用虚方法 → 恢复状态。四、构建一个 ShapeInitialize 与结果辅助方法4.1 InitializeInitializeTViewModel(shapeType, factory)是文档推荐的主要方式创建一个指定shapeType的 Shape并以强类型视图模型TViewModel作为其数据载体然后通过链式.Location(...)分配位置。仓库中TextFieldDisplayDriver的真实实现可作为标准范式见 TextFieldDisplayDriver.cspublic override IDisplayResult Display(TextField field, BuildFieldDisplayContext context) { return InitializeDisplayTextFieldViewModel(GetDisplayShapeType(context), model { model.Field field; model.Part context.ContentPart; model.PartFieldDefinition context.PartFieldDefinition; }) .Location(OrchardCoreConstants.DisplayType.Detail, Content) .Location(OrchardCoreConstants.DisplayType.Summary, Content); }OrchardCoreConstants.DisplayType定义在 OrchardCoreConstants.cs共四个取值Detail、Summary、DetailAdmin、SummaryAdmin。.Location(displayType, location)的重载允许按显示类型分别指定位置例如 Detail 页放 Content 区Summary 列表也放 Content 区而.Location(string)单参重载则设置所有显示类型的默认位置。4.2 其他结果辅助方法文档列出的其余三种方式其实现均位于 DisplayDriverBase.csView(shapeType, model)——把现有模型直接包装为ShapeViewModelTModel不执行任何初始化回调适合模型本身就是视图模型的场景Combine(...)——把多个IDisplayResult合并为一个CombinedResult让一个驱动一次返回多个 Shape例如同时输出主体和侧边栏两个 ShapeDynamic(shapeType)——构建无强类型视图模型的松散 Shape可配合Actiondynamic初始化灵活但失去编译期类型检查。除此之外基类还提供Factory(...)系列延迟创建 Shape、支持传入状态对象避免闭包分配、Copy(shapeType, model)从已有对象复制属性以及被标记为[Obsolete]的Shape(shapeType, shape)。Factory是所有驱动创建 Shape 的最终落点ShapeResult会携带驱动当前的Prefix。五、Shape 类型辅助方法在 Part/Field 驱动内部不要手写 Shape 类型字符串应使用基类提供的辅助方法让显示模式display mode与编辑器editor后缀自动生效辅助方法返回值GetDisplayShapeType(context)当前显示类型对应的显示 Shape 类型GetEditorShapeType(context)当前编辑器对应的编辑 Shape 类型以 ContentFieldDisplayDriver.cs 的实现为例GetDisplayShapeType检查PartFieldDefinition.DisplayMode()若非空则追加分隔符_Display__与模式名得到如TextField_Display__Header这样的类型GetEditorShapeType检查PartFieldDefinition.Editor()若非空则追加__与编辑器名得到如TextField_Edit__CustomEditor这样的类型。同理Part 驱动的对应实现见 ContentPartDisplayDriverTPart.cs。这样内容定义Content Definition中配置的显示模式/编辑器就能自动映射为精确的 Shape 类型进而命中精确的模板与 placement 规则。六、UpdateAsync读取编辑器表单并校验POST 回写是驱动中最容易出错的部分。标准写法TextFieldDisplayDriver的完整实现含必填、最小/最大长度校验见 TextFieldDisplayDriver.cspublic override async TaskIDisplayResult UpdateAsync(TextField field, UpdateFieldEditorContext context) { await context.Updater.TryUpdateModelAsync(field, Prefix, f f.Text); var settings context.PartFieldDefinition.GetSettingsTextFieldSettings(); if (settings.Required string.IsNullOrWhiteSpace(field.Text)) { context.Updater.ModelState.AddModelError(Prefix, nameof(field.Text), S[A value is required for {0}., context.PartFieldDefinition.DisplayName()]); } return Edit(field, context); }要点拆解绑定TryUpdateModelAsync(field, Prefix, f f.Text)从表单中按Prefix前缀读取名为Text的字段并写入field校验通过context.PartFieldDefinition.GetSettingsTextFieldSettings()读取字段设置再结合ModelState.AddModelError记录错误S[...]是本地化字符串来自IStringLocalizer回显返回Edit(field, context)重新渲染编辑器校验失败时错误信息会显示在表单上驱动接口实现会在UpdateAsync返回非空结果后调用contentPart.Apply(partFieldDefinition.Name, field)把字段写回内容项见 ContentFieldDisplayDriver.csPart 驱动同理执行part.ContentItem.Apply(typePartDefinition.Name, part)。Prefix 的作用Prefix是表单字段的命名空间前缀保证同一页面上多个相同 Shape 实例的字段不会互相冲突。从源码看字段驱动的Prefix由typePartDefinition.Name . partFieldDefinition.Name组成例如BlogPost.BodyPart 驱动的Prefix即部件名若外层存在HtmlFieldPrefix例如嵌套在 List 编辑器内还会拼接为外层前缀.部件名或外层前缀.部件名.字段名见 ContentPartDisplayDriverTPart.cs。始终使用Prefix进行绑定这是驱动正确工作的前提。七、Location字符串语法与 Fluent 构建器驱动的.Location(...)支持两种写法二者完全等价// 字符串写法 .Location(Parameters:5#Settings;1) // Fluent 写法等价 .Location(l l.Zone(Parameters, 5).Tab(Settings, 1))PlacementLocationBuilder的实现位于 PlacementLocationBuilder.cs位置字符串遵循格式[/]Zone:position#tabgroup%card|column渲染层级为Zone → Tab → Card → Column。构建器要求从.Zone()开始之后的层级可以自由跳过、任意顺序链式调用。方法字符串等价说明.Zone(Content, 5)Content:5目标 Zone 与区内位置.AsLayoutZone()/前缀标记为布局区Layout Zone.Tab(Settings, 1)#Settings;1Tab 页与页内位置.Card(Details, 2)%Details;2卡片与卡内位置.Column(Left, 1, 9)\|Left_9;1列名称、列内位置、宽度 9 栅格.Group(search)search分组用于搜索等聚合场景完整嵌套示例文档原文注释中的结果由构建器生成.Location(l l .Zone(Parameters, 5) .Tab(Settings, 1) .Card(Details, 2) .Column(Left, 3, 9)) // → Parameters:5#Settings;1%Details;2|Left_3;9位置position的比较规则在构建器文档注释中有明确说明PlacementLocationBuilder.cs按数值比较2排在10之前支持点号子位置1.5位于1与2之间支持before/after特殊值分别把 Shape 放在区的最前/最后缺省位置按0处理。.Location(string displayType, string location)与.Location(string displayType, ActionPlacementLocationBuilder)重载用于按显示类型分别配置实现见 ShapeResult.cs.Location(Summary, l l.Zone(Content, 1))注意ShapeResult内部为前两个显示类型使用内联存储、之后切换为字典存储因此你可以在一个驱动里为多个显示类型链式调用多个.Location(...)。八、驱动的注册方式驱动必须注册到 DI 容器才会被显示管线发现。在模块的Startup.cs的ConfigureServices中注册// 内容部件驱动以 MyPart 为例 services.AddContentPartDisplayDriverMyPart, MyPartDisplayDriver(); // 字段驱动以 TextField 为例 services.AddContentFieldDisplayDriverTextField, TextFieldDisplayDriver(); // 设置区块 / 通用模型驱动 services.AddScopedIDisplayDriverISite, MySettingsDisplayDriver();更常见的做法是与部件/字段本身的注册配对使用services.AddContentPartMyPart() .UseDisplayDriverMyPartDisplayDriver(); services.AddContentFieldTextField() .UseDisplayDriverTextFieldDisplayDriver();从 ContentPartServiceCollectionExtensions.cs 的实现看UseDisplayDriver等价于同时调用ForDisplayMode(...)与ForEditor(...)——即把驱动注册到所有显示模式与所有编辑器扩展方法还提供ForDisplayMode(predicate)/ForEditor(predicate)精确控制驱动只服务于特定模式/编辑器谓词接收模式名或编辑器名返回是否匹配以及RemoveDisplayDriver反注册。仓库中一个真实的应用案例OrchardCore.ContentFields/Startup.cs 中BooleanField、TextField均通过AddContentFieldTField().UseDisplayDriverTFieldDisplayDriver()注册同时还用ForDisplayMode(d !string.Equals(d, UserNames, ...))这类谓词为UserPickerField的不同显示模式注册不同的驱动第 198-199 行。九、模板与 Alternates驱动选定的 Shape 类型决定了渲染模板MyPart.cshtml、MyPart.Summary.cshtml、MyPart_Edit.cshtml等。OrchardCore 按以下顺序查找模板精确的 Shape 类型模板由 alternates候选模板名决定的更具体模板——GetDisplayShapeType/GetEditorShapeType产生的后缀显示模式_Display__xxx、编辑器__xxx会自动加入候选列表主题或模块通过placement.json的alternates节点或运行时在 Shape 元数据shape.Metadata.Alternates中追加的额外候选。Alternates 的命名规则、内容字段区分符differentiator等细节请参阅 Templates 模块文档。由于驱动返回的IDisplayResult是惰性的且ShapeResult在Displaying事件中才注入 alternates见 ContentFieldDisplayDriver.cs所以主题开发者可以在不修改模块代码的前提下通过placement.json覆盖位置、通过 alternates 覆盖模板实现完全的展示层定制。十、placement.json驱动默认位置的最终裁决驱动内.Location(...)设置的是默认位置。渲染时placement.json中的规则优先级更高可对驱动结果进行覆盖。其典型结构为按 Shape 类型分组、按 display type 细分位置{ TextField: [ { displayType: Detail, differentiator: BlogPost.Body, place: Content:5 } ] }differentiator与驱动自动生成的部件名-字段名或部件名-字段名-Shape类型对应place字符串使用与驱动.Location(...)相同的位置语法Zone、Tab、Card、Column 等。这意味着驱动只需提供合理默认值布局细节可以完全下沉到主题的placement.json中实现模块管内容、主题管布局的关注点分离。小结Display Driver 是 OrchardCore 内容管线的枢纽四个基类覆盖 Part/Field/设置区块/通用模型四种目标Display/Edit/UpdateAsync三个方法分别对应展示、编辑渲染与表单回写InitializeTViewModel配合GetDisplayShapeType/GetEditorShapeType构建类型精确的 Shape字符串或 Fluent 的.Location(...)定义默认位置最后通过Startup.cs中的AddContentPartDisplayDriver/AddContentFieldDisplayDriver/UseDisplayDriver完成注册并可由placement.json与 alternates 在主题层进行最终裁决。掌握这套流程即可为任意内容类型编写可扩展、可主题化的展示逻辑。赞分享CMS后端Web框架【免费下载链接】OrchardCoreOrchard 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.项目地址https://gitcode.com/gh_mirrors/or/OrchardCore点击查看免费下载相关推荐Wox 插件清单 plugin.json 完整规范字段、SettingDefinitions、QueryRequirements 与 Features 权威指南Wox 插件清单 plugin.json 完整规范字段、SettingDefinitions、QueryRequirements 与 Features 权威指桌面应用AI 应用插件系统Webiny-js扩展开发实战构建自定义内容模型与字段类型Webiny js扩展开发实战构建自定义内容模型与字段类型 Webiny js作为开源无服务器企业CMS内容管理系统Content ManagementCMS后端前端gh_mirrors/exam/examples权威教程模型部署容器化方案gh_mirrors/exam/examples权威教程模型部署容器化方案 你是否还在为深度学习模型部署时的环境配置头痛本地开发环境与服务器不一致、依赖库版示例工程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考