DaST Concept: 表单和 MVC 的更简单、更智能、更强大的替代方案。






4.65/5 (13投票s)
纯 HTML 模板和统一的代码隐藏是 Web 开发的未来。本文介绍了 ASP.NET DaST 渲染引擎及其底层的 DaST 概念,它将 Web 应用架构提升到一个新的水平,摆脱了标准 ASP.NET Forms 和 MVC 相关的所有问题和复杂性。
摘要
DaST 模式和 ASP.NET DaST 渲染引擎是 ASP.NET Scopes Framework 及其底层服务器页面技术的进一步发展,该技术基于我于 2010 年 11 月在第一篇文章中提出的完全模板化的数据作用域树。从现在开始,该模式将被称为 DaST,代表 Data Scope Trees(数据作用域树)。在使用 DaST 一段时间后,我认为这种方法开启了无与伦比的 Web 开发机会,并很有可能在不久的将来完全取代标准的 ASP.NET Forms 和 MVC 模式。
本文由 4 部分组成。首先,我将介绍一个新的 VideoLibrary DEMO 应用程序,该应用程序将在实际网站上展示 DaST 模式的全部威力。该应用程序将作为 ASP.NET DaST 框架所有后续版本的一个很好的操作指南示例和 DaST 技术演示器。第二部分,我将简要概述 DaST 概念本身,并将其 VideoLibrary 页面分解为作用域树,以便第一次接触这项技术的人能够了解新的 DaST 方法及其优势。第三部分,我将仔细研究演示应用程序的后端架构和实现,并重点介绍自 11 月份第一个 Alpha 版本以来发生的所有重要语法和设计更改。本节仅面向阅读了我上一篇文章的读者。最后一部分,我将分享一些近期框架开发计划。
还有一件事 :) 鉴于许多读者批评我上一篇文章太长,我将尽量保持这篇文章简短、精炼。但是,如果您是第一次接触这个框架并希望在您的项目中使用它,那么本文是不够的,您需要阅读我 11 月份更详细、更深入的Scopes Framework 文章。
目前,所有 DaST 相关的消息和活动都发布在DaST 开发网站上。请访问该网站了解框架更新和重要更改。ASP.NET DaST 渲染引擎的最新版本以及 VideoLibrary DEMO 的源代码位于该网站的下载部分。
!!!更新(2011 年 5 月 11 日):ASP.NET DaST 渲染引擎项目已于 5 月 9 日正式开源!我们正在寻找开源开发者,如果您觉得自己可以为项目做出贡献,欢迎联系 DaST 团队。DaST 开源信息网站在此。SourceForge 上的 DaST 项目控制台主页在此。公开的 DaST 讨论论坛在此(支持匿名访问)
目录
VideoLibrary DEMO - 技术演示器
为了展示 DaST Web 开发模式相对于标准 Forms 和 MVC 方法的优势,我创建了一个名为 VideoLibrary 的示例应用程序,它将同时作为示例、教程和技术演示器。VideoLibrary DEMO 是一个基于 ASP.NET DaST 框架构建的视频门户原型。该示例应用程序具有复杂而动态的 UI,包含了构建富 Web 2.0 站点所需的大部分典型功能元素,并演示了如何使用 DaST 渲染引擎实现这些功能。以下是 VideoLibrary 中实现的一些流行且受欢迎的 Web 2.0 功能:
- 大量部分更新的区域,具有随机且复杂的嵌套结构。
- 延迟加载并进行部分更新,以显示需要数据访问的内容。
- 带有延迟加载其主体内容的详细信息对话框。
- 大量 jQuery 客户端小部件 + jQuery 主题
以下是该应用程序的一些屏幕截图(图 1 和图 2)以及应用程序在用户级别上操作的简要说明。虽然该应用程序本身没有太多实际功能,主要作为演示器,但它可以在现实世界中用作类似应用程序的原型。请注意,图 1 和图 2 显示了应用程序在 2 种不同的 jQuery UI 主题下的效果。
图 1:VideoLibrary DEMO 初始外观
该应用程序显示了视频封面图的分页选择(参见图 1)。当然,没有真正的视频——我们只是使用了带有彩色文本的黑色矩形。文本颜色定义了视频的类别:红色类别、绿色类别等。每个封面图的右上角都有一个“+”按钮。单击它,视频将被添加到左侧的播放列表中,按钮变为“-”。单击“-”将视频从播放列表中移除。鼠标悬停时,封面图会显示一个“i”按钮,该按钮会调用详细信息对话框。
图 2:VideoLibrary DEMO 详细信息对话框
该对话框(参见图 2)显示了更多详细信息以及同一类别(即具有相同颜色)下视频封面图的分页列表。此列表中的封面图与播放列表和选择网格中的工作方式相同。对话框是选项卡式的。您可以切换到另一个选项卡来添加视频评论和评分。请注意,您所做的所有更改仅保存在当前会话中。您还可以通过选择左上角的 jQuery UI 主题来调整应用程序的外观和感觉。
所有应用程序数据均来自通过 XML 文件和 LINQ 模拟的数据层。由于此应用程序是 DEMO,我将其设置为所有更改(添加评论、评分、播放列表等)仅在当前会话中持久化,并在会话结束时被丢弃。在图 3 中,您可以看到用作数据源的 XML 文件的一部分。它的结构很简单,您可以了解 VideoLibrary 数据是如何组织的。
图 3:后端数据 XML
尽管漂亮且吸引人的 UI 元素是应用程序的重要组成部分,但 DaST Web 开发模式的所有优势只有在我们查看应用程序后端代码时才会显现出来。DaST 架构的 VideoLibrary 后端清晰、透明且统一,可实现无与伦比的前端灵活性,并允许 Web 应用程序 UI 几乎无限的复杂性。
如果您想立即运行 VideoLibrary DEMO 应用程序的实时测试,可以在DaST 开发网站上进行,我将始终在那里部署该应用程序的最新版本。整个 VideoLibrary DEMO 源代码可作为本文附件下载。我建议您获取源代码,浏览并熟悉它,因为在本文的其余部分,我将使用此示例代码进行所有解释。
DaST 概念概述
您认为有可能创建一个超越标准 Forms 和 MVC 在易用性、架构、灵活性、性能、表示分离以及其他所有重要 Web 开发属性方面的最终服务器页面渲染引擎吗?作为对这个问题的回答,我想介绍基于新的 DaST Web 开发模式的 ASP.NET DaST 渲染引擎。该模式的名称代表 Data Scope Trees(数据作用域树),稍后您就会明白其含义。即使您是一名经验丰富的 Web 应用程序程序员或架构师,并且还不熟悉这种模式,我也保证本文的其余部分将改变您对基于服务器页面 Web 应用程序设计和开发的看法。
整个 DaST 概念源于一个非常简单的想法,即世界上每个网站不过是一堆由服务器端逻辑生成并通过客户端浏览器呈现给用户的数据。您在网站上看到的一切都只是包装在 HTML 文本中的一些数据!这意味着整个页面渲染过程可以视为两个独立的步骤:1) 生成一些数据值;2) 以 HTML 形式将这些数据值呈现给客户端。
为了在第一步中完成数据生成,我们需要某种代码隐藏类。抽象地说,这种类的输出是一组字符串值。在 DaST 框架中,这个类被称为控制器(类似于 MVC 中的)。第二个渲染步骤更有趣。在控制器生成所有字符串值后,我们需要将它们与一些标记混合,并将 HTML 输出提供给客户端。听起来很简单,但如何在技术上实现呢?每个有经验的开发人员都知道标准 Forms 或 MVC 平台是如何通过复杂的资源消耗型页面生命周期、渲染多个单独的服务器控件、应用主页、调用重复控件的数据绑定实现、处理事件等等来渲染页面的。这个过程相当复杂,我们将尝试从另一个角度处理渲染任务。为了将我们准备好的数据渲染成 HTML,我们将采用一个完整的有效 HTML 模板,并将准备好的字符串值插入到模板的正确位置。插入数据值的位置由特殊的占位符标记,在渲染阶段会被实际数据值替换。就是这样!我们只需要一个纯粹的HTML 模板,仅此而已——没有服务器控件,没有页面生命周期,没有数据绑定——我们一次性抛弃了所有这些复杂性!
看看图 1 中的演示应用程序。现在想象一下,如果我们有一系列准备好的数据值(例如所有名称、描述、页码等)和一个完整的HTML 模板,其中占位符位于要插入值的位置。我们的渲染过程会是什么?嗯,整个渲染将简化为简单的搜索和替换操作,以将占位符替换为实际值。这基本上就是 DaST 渲染页面所做的一切!
数据作用域树
当然,我说一堆数据被应用于模板时,我省略了一些技术细节。实际上,这个过程必须更加精细,因为我们还需要一种方法来管理我们的数据值,以便它们只被应用于正确的位置。仅仅有占位符是不够的,因为我们根本不想一次性生成所有数据值,而且我们可能也做不到。最好是首先生成页面某个区域的值,然后是另一个区域,依此类推。此外,我们还需要一种方法来为可以嵌套在任何级别的另一个重复器中的重复内容提供数据。那么 Ajax 和部分页面更新呢?考虑到所有这些需求,我最终得到了数据作用域和数据作用域树的概念,它们成为了 DaST 模式(因此得名)以及基于它的框架的核心。
数据作用域只是一个一组相关数据值的集合。相关性标准由开发人员选择。数据作用域可以应用于HTML 模板的特定区域,这意味着该区域中的占位符将被数据作用域中的相应数据值替换。结果是,我们得到这个特定模板片段的最终 HTML 输出。通过将这种技术扩展到整个页面,我们可以将多个数据作用域应用于HTML 模板的所有区域,以完全渲染模板并将页面输出给客户端。数据作用域还可以嵌套形成一个称为数据作用域树的层次结构。最后,我断言**任何复杂程度的页面都可以被视为一个带有数据作用域树的 HTML 模板**。Voilà!:)
因此,Web 开发人员必须完成的第一件事是将应用程序 UI 分解为一组随机嵌套的数据作用域。根据我的经验,选择正确的数据作用域嵌套结构需要一些 DaST 特有的技能,这些技能在经过几次练习后会逐渐掌握。让我们为图 1 中所示的演示应用程序的一部分 UI 执行此操作。生成的数据作用域如图 4 所示。
图 4:VideoLibrary 部分 UI 分解为数据作用域
首先,总会有一个根作用域(NULL 作用域),它充当所有其他作用域的父级。然后我们有 PageSizeRepeater 和 SiteThemeRepeater 来输出分页大小和 jQuery 主题下拉列表的项(参见图 1)。接下来,我们在左侧有几个其他数据作用域来形成播放列表 UI。PlaylistHeader 作用域显示当前分页信息。PlaylistPager 作用域包含播放列表项分页器。PlaylistRepeater 需要显示多个视频封面图,这些封面图由嵌套的 VideoItem 作用域表示。每个 VideoItem 作用域都有 2 个嵌套作用域:AddButton 和 RemoveButton。思路是,当项目不在播放列表中时只显示 AddButton 作用域,否则显示 RemoveButton 作用域。接下来,在页面右侧,我们有一个视频项选择网格。此网格的结构与播放列表完全相同,只是同一级别的数据作用域名称不同。现在,如果我们图 4 中的嵌套数据作用域描绘成树状,我们将得到一个标识我们页面的数据作用域树。VideoLibrary 应用程序的完整数据作用域树如图 5 所示。树的上部对应图 1 中的 UI,我们刚刚将其分解。来自 DetailsPopup 作用域的底部子树对应图 2 中的 UI。带有控制器的数据作用域具有红色背景,稍后将对此进行解释。
图 5:VideoLibrary 完整数据作用域树
模板中的数据作用域
好的,在逻辑上将 UI 分解为数据作用域之后,下一个问题是如何将特定的数据作用域指向模板中的特定区域。解决方案很简单。由于我们的数据作用域树构建方式代表了文档的逻辑结构,我们只需要在HTML 模板中使用相同的结构,将相应区域包装在代表数据作用域应用的边界的有效 DIV 容器元素中。这意味着,这些 HTML 模板中的容器具有与数据作用域树定义的层次结构相同的结构。每个数据作用域都由其名称标识,例如“PlaylistPager”或“VideoItem”。相同的名称应指定在相应 HTML 容器元素的 scope
属性中。因此,系统不关心您的模板有多复杂,也不关心它有什么类型的标记——它只关心嵌套的作用域 DIV,即模板中的数据作用域树。例如,如果我们想为图 5 中的数据作用域树创建一个有效模板,该模板的一部分可能看起来像列表 6 中的标记。
your markup here ...
your markup here ...
your markup here ...
your markup here ...
your markup here ...
your markup here ...
rest of template follows ...
请注意,目前 DaST 渲染引擎仅使用 DIV 作为作用域容器是其局限性,将在框架的下一个版本中取消。
控制器概述和数据绑定
为了生成模板的实际数据,开发人员必须实现一个控制器类。我将生成相应模板数据的过程称为数据绑定。对于树中的每个数据作用域以及模板中的相应作用域 DIV,控制器都包含一个名为绑定处理程序的数据绑定函数。绑定处理程序的输出基本上是一组当前数据作用域的值。
最简单的情况下,有一个单一的控制器负责为作用域树中的所有数据作用域生成数据。正常情况下,有多个控制器,每个控制器只负责作用域树的一部分。当一个控制器附加到数据作用域时,它将负责为该作用域及其所有子作用域生成数据,除非这些子作用域有自己的控制器。
再次查看图 5——红色背景上的数据作用域是附加了控制器的。NULL 作用域总是附加至少一个控制器,称为根控制器。其他控制器是子控制器。就像 Forms 中的用户控件或 MVC 中的部分视图一样,DaST 中的子控制器应该在适当的时候用于将某个通用功能片段分离出来。我们演示应用程序中的一个例子是将 PagerController
附加到 PlaylistPager、VideoItemPager、AlikeVideosPager 和 CommentsPager 数据作用域(参见图 5)。
渲染过程
现在我将重新定义 DaST 渲染过程,以控制器和绑定处理程序为术语。为了渲染整个页面,框架从为树中的NULL 数据作用域生成数据开始,通过调用根控制器中的相应绑定处理程序,并递归地对树中的所有数据作用域重复此操作,调用负责任的控制器上的适当处理程序。数据作用域的处理顺序始终是特定且一致的。从树遍历算法的角度来看,框架使用后序遍历,并进行自顶向下的中序遍历。回想一下高中学的图,这实际上称为从右到左的中序遍历,但由于将作用域树水平描绘(如我在图 5 中所做)更为合适,我称之为自顶向下。选择这种遍历顺序是因为 HTML 模板中的实际作用域 DIV 标签正是按此顺序遇到的。
因此,使用 DaST 渲染引擎渲染页面所需做的就是 1) 创建一个类似于列表 6 中所示的HTML 模板,然后 2) 为其实现一个控制器(或为多个模板实现多个控制器)。列表 7 显示了我们演示应用程序的根控制器(VideoLibraryController.cs)的高层大纲。这个根控制器是附加到图 5 上NULL 作用域的。生成相应作用域值的绑定处理程序位于第 87-193 行。这些处理程序函数的名称由开发人员选择。第 10 行您可以看到 SetTemplate()
函数——这是我们告诉控制器使用哪个模板的方式。第 15 行的 SetModel()
函数用于将绑定处理程序或子控制器分配给树中的数据作用域。最后,第 45-73 行是响应用户操作而调用的操作处理程序(可以将其视为事件的对应项)。
8 public class VideoLibraryController : ScopeController
9 {
10 public override void SetTemplate(SetTemplateArgs template) { ... }
14
15 public override void SetupModel(ControllerModelBuilder model) { ... }
43
44
45 private void Action_PageSizeChanged(ActionArgs args) { ... }
58
59 private void PlaylistPager_PageChanged(ActionArgs args) { ... }
65
66 private void VideoItemPager_PageChanged(ActionArgs args) { ... }
72
73 private void VideoItem_PlaylistUpdated(ActionArgs args) { ... }
85
86
87 private void ROOT_DataBind() { ... }
104
105 private void PageSizeRepeater_DataBind() { ... }
118
119 private void SiteThemeRepeater_DataBind() { ... }
130
131 private void PlaylistHeader_DataBind() { ... }
165
166 private void PlaylistRepeater_DataBind() { ... }
181
182 private void VideoItemGridInfo_DataBind() { ... }
192
193 private void VideoItemRepeater_DataBind() { ... }
208 }
操作和 AJAX
数据作用域树的一个巨大优势是它们允许最清晰地定义部分页面更新,并且与其他所有现有框架相比,它们在使用 Web 应用程序的 AJAX 功能方面最为简单。DaST Ajax 的底层思想就像 DaST 中的一切一样简单——数据作用域树中的每个作用域都可以在异步回发期间随时单独刷新!当数据作用域被刷新时,其在作用域树中的所有子数据作用域也会被刷新。从数据绑定的角度来看,这意味着系统会重新调用与刷新作用域相对应的绑定处理程序,以重新生成数据值,更新模板的部分内容,并将它们输出回客户端。数据作用域树的遍历顺序在回发时保持,从而维护了绑定处理程序调用的顺序一致性。
接下来,异步回发不是随机发生的,而是响应浏览器中的某些事件(例如按钮点击、计时器滴答等)。DaST 渲染引擎提供了一个简单轻量级的机制,允许在客户端引发事件,并在回发时在控制器类中处理这些事件。在 DaST 中,事件的对应项称为操作。每个操作都有一个名称和一个字符串参数。这不是一个限制,因为字符串参数可以包含任何 JSON 可序列化的对象。当发生操作时,异步回发会到达控制器,并执行适当的操作处理程序(请参阅列表 7 的第 45-73 行)。操作处理程序是我们进行一些处理并指示特定数据作用域刷新指令的地方。
DaST 模式总结
好的,到目前为止,您应该对 DaST 模式和 DaST 渲染引擎的实际内容以及它们与今天可供开发人员使用的其他模式和框架的不同之处有一个清晰的顶层画面。下面我将总结使用 DaST 模式在 Web 应用程序中的最明显的好处。
- 演示与后端代码分离的最大可能级别。
- 如上所述,卓越的应用程序后端可测试性和 TDD 的易用性。
- 使用符合 W3C 标准的纯HTML 模板来控制 UI 标记的每一个字节。
- 因此,无与伦比的 UI 灵活性、可维护性和简洁性。您无需深入研究服务器控件的标记,而是处理简单的 HTML!
- 能够通过在模板中操作 HTML 标记,在 10 分钟内完全更改页面布局和外观。显然,这无需重新编译网站!
- 最短的学习周期。无需阅读 400 页的书籍即可开始创建高度动态和复杂的站点——您只需要了解如何将 UI 分解为 i>作用域树,以及如何在控制器类中实现操作和绑定处理程序!
- 所有后端设计都变得简单、清晰、统一。无论您的应用程序 UI 有多复杂,无论您的数据作用域树有多大,后端总是相同的——只是一个包含操作和绑定处理程序列表的控制器!
- 没有页面生命周期。没有服务器控件。再也不用纠结如何让控件以某种方式渲染——您的HTML 模板和绑定处理程序将完全从头到尾控制您的渲染过程。
- 原生支持 AJAX 部分更新,无需一行代码或额外标记。与 Forms 中的更新面板进行比较 :)))
- 绑定处理程序仅在回发时针对页面刷新部分被重新调用,这意味着您的代码仅在需要时执行!再次与 Forms 进行比较,在 Forms 中,页面会经历完整的生命周期,执行大量不必要的代码。
- DaST 不否认 Forms 的任何标准功能,而是对其进行补充。我们喜欢的会话管理、应用程序缓存、身份验证类等仍然可以在控制器中的操作和绑定处理程序中使用。标准 Forms 应用程序可以与 DaST 应用程序共存,从而允许从 Forms 到 DaST 的平滑逐步过渡。
- 总而言之,根据我的经验,应用程序开发时间最多可减少 90%(不是玩笑)。
本文的主要目的是概述框架新版本中的重要更改。如果您是第一次看到这个概念,并想详细了解如何创建 HTML模板和实现控制器,您需要阅读这篇文章,它更深入、更详细。或者您可以等待 DaST 开发网站 http://www.rgubarenko.net 上的文档和教程可用,但我无法保证任何日期。
重要的框架更改。VideoLibrary 后端。
在本节中,我将快速回顾自上一版本框架以来所有重要的语法和设计更改。我不会深入探讨太多后端细节,因为整个机制已经在我去年 11 月的文章中进行了描述,我只会为阅读过该文章并已熟悉 Scopes Framework 的读者重点介绍区别。因此,以下各节只是两个版本之间差异的列表。
1) 二进制文件和命名空间
框架二进制文件和命名空间发生的变化如下:
- 框架 DLL 的名称已更改为 AspNetDaST.dll(位于演示站点的 Bin 文件夹中)。
- 开发人员使用的所有公共类都位于
AspNetDaST.Web
命名空间中。
2) ScopesManagerControl 已移除
在 Scopes Framework 中,我使用了临时的解决方案,即必须将 ScopesManagerControl
添加到 ASPX 页面才能使其与框架一起工作。现在这个已经移除。在 DaST 渲染引擎中,我们继承自 AspNetDaST.Web
命名空间中的 DaSTPage
类,并且只实现一个 ProvideController()
方法。您无需向 ASPX 页面添加任何标记——DaSTPage
父类会处理这一切。列表 8 显示了 VideoLibrary.aspx.cs 代码隐藏类的列表。一切都应该很清楚:我们在第 14 行实例化根控制器,并在第 15 行启用部分更新。请注意,目前 EnablePartialUpdates
必须始终设置为 TRUE
。这是因为我还没有决定是否需要支持完全回发。在 Forms 中,异步回发可选的原因是 UpdatePanel 控件会增加显着的复杂性和性能开销。在 DaST 中,部分刷新是原生的,我看不出让它们可选的理由。
8 using AspNetDaST.Web;
9
10 public partial class VideoLibrary : DaSTPage
11 {
12 protected override void ProvideController(PageSetup setup)
13 {
14 setup.RootController = new VideoLibraryController();
15 setup.EnablePartialUpdates = true;
16 }
17 }
3) 数据绑定更改
正如您可能在列表 7 中注意到的,绑定处理程序中的参数已被消除。处理程序参数曾用于为当前作用域添加占位符替换。从现在开始,占位符替换直接添加到作用域实例中,您可以使用 CurrentPath
和 ControlPath
树导航器访问该实例。这些渲染的作用域树导航器的含义也略有改变:
- 在操作或绑定处理程序内部使用的
ControlPath
始终指向当前控制器附加到的作用域。 - 在绑定处理程序内部使用的
CurrentPath
指向当前绑定的作用域。在操作处理程序内部使用的CurrentPath
指向客户端 JavaScriptAction(..)
调用中指定的作用域作为目标作用域。
只需查看显示 DetailsPopupController.cs 类中一个绑定处理程序的列表 9。在第 202-203 行,我们不再像以前那样调用传递为参数的绑定器的 Replace(..)
,而是直接在作用域实例上调用 Replace(..)
,使用指向当前 ReviewsInfo 作用域的 CurrentPath
。
195 private void ReviewsInfo_DataBind()
196 {
197 decimal rating = CurrentPath.Scope.Param("Rating");
198 int commCount = CurrentPath.Scope.Param("CommCount");
199
200 CurrentPath.Scope.ShowConditionArea("comm-empty:yes", commCount <= 0);
201 CurrentPath.Scope.ShowConditionArea("comm-empty:no", commCount > 0);
202 CurrentPath.Scope.Replace("{Rating}", rating.ToString("0.0"));
203 CurrentPath.Scope.Replace("{CommCount}", commCount);
204
205 CurrentPath.Rew(1).Fwd("ReviewsInfo2").Scope.ShowConditionArea("comm-empty:yes", commCount <= 0);
206 CurrentPath.Rew(1).Fwd("ReviewsInfo2").Scope.ShowConditionArea("comm-empty:no", commCount > 0);
207 CurrentPath.Rew(1).Fwd("ReviewsInfo2").Scope.Replace("{Rating}", rating.ToString("0.0"));
208 CurrentPath.Rew(1).Fwd("ReviewsInfo2").Scope.Replace("{CommCount}", commCount);
209 }
乍一看,数据绑定语法似乎变得更复杂了,但实际上这种设计为我们带来了巨大的编程优势,因为我们现在能够从其他作用域的绑定处理程序内部进行作用域的数据绑定。这很棒,因为如果一个作用域很简单,只包含一对值,为什么我要在我的控制器类中创建一个单独的绑定处理程序来填充它呢?此新功能在DetailsPopupController
类的第 207-208 行(参见列表 9)中使用,我们使用导航器指向另一个 ReviewsInfo2 作用域,并向其添加相同的占位符替换。
4) 使用作用域实例
在新版本的 DaST 渲染引擎中,所有框架调用都变得更加统一。现在,所有与特定作用域操作相关的方法和属性都可以通过作用域实例对象访问。数据绑定函数(其中一些我在上一节已经讨论过)是这类方法的一部分。另一组方法提供对作用域上下文参数的操作。图 10 的图表显示了 RenderedScopeInstance
类的公共接口。
图 10:RenderedScopeInstance 类的公共接口。
下面是对所有类成员的简要解释。大多数成员对您来说应该非常熟悉,因为它们来自 Scopes Framework。即使名称已更改,含义仍然相同。标记为“(NEW)”的成员是当前版本框架中添加的成员。
- RenderTyRenderType (NEW) - 获取或设置作用域渲染的类型。稍后解释。
- ScopeClientID - 渲染页面时看到的作用域容器的 ID。
- HasParam - 指示是否已设置具有指定名称的参数。
- InitParam (NEW) - 如果尚未设置参数,则设置该参数。
- Method (NEW) - 调用作用域上下文中的方法。稍后解释。
- Param (NParam (NEW) - 获取强类型参数。
- Refresh - 导致目标作用域的部分更新。
- Repeat - 重复当前作用域的内容。
- Replace, ReplaceRange - 添加占位符替换。
- RestartRepeater (NEW) - 将内容重复重置为 0。稍后解释。
- SetParamSetParam, SetParams - 为目标作用域设置单个或一系列参数。参数可以是任何可序列化为 JSON 的对象。
- ShowConditionArea (NEW) - 美观的功能,允许直接操作作用域标记。稍后解释。
5) 作用域内容重复
在 DaST 框架中,每个作用域的内容默认输出一次,意味着生成的 HTML 片段就是该作用域容器在模板中拥有的内容。为了重复作用域内容,我们应该调用目标作用域上的 Repeat(..)
函数。我们还有一个 RestartRepeater(..)
方法(参见图 10),可以将重复计数重置为 0。如果调用此方法且后面没有调用 Repeat(..)
,则该作用域将不输出任何内容。列表 11 显示了 PlaylistRepeater 作用域如何输出其视频封面图列表。首先,在第 168-170 行,我们从附加了 PagerController
的 PlaylistPager 作用域中检索分页器值。然后,我们从模拟数据层获取视频项列表。然后,在第 173 行,我们调用 RestartRepeater(..)
,最后,在第 174-179 行,我们只是循环遍历对象列表,在每次运行时调用 Repeat(..)
,并将视频项对象传递给具有附加了 VideoItemController
的 VideoItem 作用域。
166 private void PlaylistRepeater_DataBind()
167 {
168 int startItemIdx = CurrentPath.Rew(1).Fwd("PlaylistPager").Scope.Param("StartItemIdx");
169 int pageSize = CurrentPath.Rew(1).Fwd("PlaylistPager").Scope.Param("PageSize");
170 int itemTotalCount = CurrentPath.Rew(1).Fwd("PlaylistPager").Scope.Param("ItemTotalCount");
171
172 object[] videoItems = DataLayer.GetPlayItems(startItemIdx, pageSize);
173 CurrentPath.Scope.RestartRepeater();
174 for (int i = 0; i < videoItems.Length; i++)
175 {
176 CurrentPath.Scope.Repeat();
177 CurrentPath.Fwd(i, "VideoItem").Scope.SetParam("ItemIndex", startItemIdx + i);
178 CurrentPath.Fwd(i, "VideoItem").Scope.SetParam("VideoItemObject", videoItems[i]);
179 }
180 }
此版本框架中的另一项新功能是,我们可以将通用对象保存为作用域参数。在列表列表 11 的第 178 行,我们将整个视频项对象设置为 VideoItem 作用域的参数。系统接受任何对象作为作用域参数,只要该对象可序列化为 JSON,即可以保存为简单字符串。
6) 作用域可见性
在上一版本框架中,数据作用域可以设为不可见,意味着该作用域的输出为空字符串。在当前版本中,每个数据作用域都有更多的可见性选项,这通过作用域实例的 RenderType
属性设置(参见图 10)。此属性是一个枚举类型,其可能值总结如下:
Normal
- 作用域正常渲染。Empty
- 作用域仅作为容器渲染,即该作用域的输出是一个空的 DIV 容器。该作用域及其子树中的所有作用域的绑定处理程序均不调用。该作用域的参数会保留。子树中作用域的参数不保留。None
- 与 Empty 相同,不同之处在于它被渲染为空字符串,并且其参数不保留。
请注意,当作用域刷新时,其 RenderType
会自动重置为 Normal
。此外,重要的是要理解,如果可见性设置为 None
,您将无法刷新此作用域,因为没有 HTML 容器可以接受新内容。最后,您必须计划在作用域树中使用哪些作用域来保存参数,因为当您将作用域可见性设置为Empty或None时,其所有子作用域参数都会被丢弃。
让我们来看一个示例,演示如何实现详细信息对话框中的延迟加载效果。列表 12 显示了参与延迟加载的最重要的后端函数。在客户端,当您单击任何视频封面图上的“i”按钮时,将使用 jQuery Dialog 插件弹出对话框。最初,对话框只有一个动画加载器图标,没有其他内容,因为在 ROOT_DataBind()
方法中,我们将 DetailsContent 作用域的渲染类型设置为 Empty
。这意味着在初始加载时,**DetailsContent** 作用域的子作用域的绑定处理程序均未调用。当对话框打开时,会引发“LoadContent”操作并在第 51 行处理。在此操作处理程序内部,我们刷新 DetailsContent 作用域,并将其“VideoID”参数设置为从客户端传递的操作参数 ID。接下来,由于 DetailsContent 作用域被刷新,系统将重新调用其绑定处理程序以及所有子作用域在遍历顺序中的绑定处理程序。因此,将调用第 170 行的 DetailsContent_DataBind()
。“VideoID”在操作处理程序中填充,并在绑定处理程序中使用以检索实际视频项,并使用其值添加占位符替换。因此,在执行此绑定处理程序后,系统会将更新后的输出传输到客户端,DetailsContent 作用域将更新为实际视频项信息,即我们的延迟加载正好按预期工作。请注意第 174 和 175 行,我们隐藏了 CommentsContent 和 AddCommentScreen,以便将相同的延迟加载技术应用于它们,但这次是在打开的对话框内。
51 private void Action_LoadContent(ActionArgs args)
52 {
53 string videoID = (string)args.ActionData;
54
55 ControlPath.Fwd("DetailsContent").Scope.Refresh();
56 ControlPath.Fwd("DetailsContent").Scope.SetParam("VideoID", videoID);
57 }
...
160 private void ROOT_DataBind()
161 {
162 ControlPath.Fwd("DetailsContent").Scope.RenderType = ScopeRenderType.Empty;
163
164 CurrentPath.Scope.Replace("{DetailsContent_ScopeID}", CurrentPath.Fwd("DetailsContent").Scope.ScopeClientID);
165 CurrentPath.Scope.Replace("{CommentsContent_ScopeID}", CurrentPath.Fwd("DetailsContent", "CommentsContent").Scope.ScopeClientID);
166 CurrentPath.Scope.Replace("{AddCommentScreen_ScopeID}", CurrentPath.Fwd("DetailsContent", "AddCommentScreen").Scope.ScopeClientID);
167 CurrentPath.Scope.Replace("{ErrorDisplay_ScopeID}", CurrentPath.Fwd("DetailsContent", "AddCommentScreen", "ErrorDisplay").Scope.ScopeClientID);
168 }
169
170 private void DetailsContent_DataBind()
171 {
172 string videoID = CurrentPath.Scope.Param("VideoID");
173
174 CurrentPath.Fwd("CommentsContent").Scope.RenderType = ScopeRenderType.Empty;
175 CurrentPath.Fwd("AddCommentScreen").Scope.RenderType = ScopeRenderType.Empty;
7) 方法处理程序
有时,设置作用域参数的能力还不够,我们希望调用控制器中的实际方法来执行某些活动。此功能已添加到框架的新版本中。列表 13 显示了如何为 PagerController
类注册 UpdatePagerValues(..)
方法。将来我可能会提出更智能的东西,但在当前版本中,有效的方法必须接受单个对象参数并返回对象结果。第 110 行是 UpdatePagerValues(..)
私有方法。只允许一个参数不是问题,因为我可以随时传递 JSON 对象图并从中获取单独的值。第 25 行注册了方法处理程序,使其可以从其他控制器调用。
18 public override void SetupModel(ControllerModelBuilder model)
19 {
20 model.SetDataBind(new DataBindHandler(ROOT_DataBind));
21
22 model.HandleAction("NextPage", new ActionHandler(Action_NextPage));
23 model.HandleAction("PrevPage", new ActionHandler(Action_PrevPage));
24
25 model.RegisterMethod("UpdatePagerValues", new MethodHandler(UpdatePagerValues));
26 }
...
110 private object UpdatePagerValues(object jsonData)
111 {
112 int startItemIdx, pageSize, itemTotalCount;
113 if (DaSTUtils.HasValue("StartItemIdx", jsonData)) startItemIdx = (int)DaSTUtils.GetValue("StartItemIdx", jsonData);
114 else startItemIdx = ControlPath.Scope.Param("StartItemIdx", 0);
115 if (DaSTUtils.HasValue("PageSize", jsonData)) pageSize = (int)DaSTUtils.GetValue("PageSize", jsonData);
116 else pageSize = ControlPath.Scope.Param("PageSize", 1);
117 if (DaSTUtils.HasValue("ItemTotalCount", jsonData)) itemTotalCount = (int)DaSTUtils.GetValue("ItemTotalCount", jsonData);
118 else itemTotalCount = ControlPath.Scope.Param("ItemTotalCount", 0);
119
120 startItemIdx = Math.Min(startItemIdx, itemTotalCount - 1);
121 startItemIdx = ((int)(startItemIdx / pageSize)) * pageSize;
122
123 // set recalculated values
124 ControlPath.Scope.SetParam("StartItemIdx", startItemIdx);
125 ControlPath.Scope.SetParam("PageSize", pageSize);
126 ControlPath.Scope.SetParam("ItemTotalCount", itemTotalCount);
127
128 return null;
129 }
接下来,列表 14 显示了如何从 VideoLibraryController
的操作处理程序中调用该方法。在第 77 行,我们首先指向附加了 PagerController
的 PlaylistPager 作用域,并调用 Method(..)
函数(参见图 10)来调用先前按名称注册的方法。注意我是如何传递通用对象作为方法参数的。
73 private void VideoItem_PlaylistUpdated(ActionArgs args)
74 {
75 // pass total items count to parent scope
76 int itemTotalCount = DataLayer.GetPlayItemCount();
77 ControlPath.Fwd("PlaylistPager").Scope.Method(
78 "UpdatePagerValues", new { ItemTotalCount = itemTotalCount });
79
80 ControlPath.Fwd("PlaylistHeader").Scope.Refresh();
81 ControlPath.Fwd("PlaylistPager").Scope.Refresh();
82 ControlPath.Fwd("PlaylistRepeater").Scope.Refresh();
83
84 ControlPath.Fwd("VideoItemRepeater").Scope.Refresh();
85 }
这里出现第一个问题是,为什么我们使用所有这些方法注册技术而不直接公开控制器的公共接口并直接调用函数?嗯,在 DaST 中永远不应该这样做,因为请记住,每个数据作用域始终只有一个控制器实例,该实例会为所有重复的作用域实例重用。因此,通过注册机制调用控制器方法,我们只是允许系统在目标控制器实例上维护正确的上下文。
8) 直接模板操作
这是新 DaST 框架中我最喜欢的功能之一。到目前为止,我们只通过将占位符替换为实际值来修改作用域内容。如果我们需要根据特定条件隐藏或显示某些区域,我们可以将这些区域封装到实际的作用域中,并使用 RenderType
属性来隐藏和显示这些作用域。但问题是我们可能需要多个条件区域,为每个区域创建单独的作用域会在模板中变得混乱,并用大量不必要的简单代码(用于隐藏、显示和刷新这些数据作用域)淹没后端控制器。解决方案是允许直接的作用域标记操作,这完美地补充了现有的占位符替换。我添加的第一个操作是 ShowConditionArea(..)
函数(参见图 10),用于显示或隐藏作用域标记的一部分。
例如,ReviewsInfo 作用域(参见图 5)负责详细信息对话框(参见图 2)第二个选项卡标题中显示的文本。当当前视频有评论时,文本应显示为 “XX 条用户评论”。如果没有评论,文本将显示为 “0 条用户评论”。但是,如果我希望显示更友好的消息,比如 “尚未有用户评论” 呢?现在答案很简单——使用条件区域!列表 15 显示了带有 ReviewsInfo 作用域的模板的一部分。要创建条件区域,标记必须包含在 <!--showfrom:condition_name-->
和 <!--showstop:condition_name-->
之间。此特殊格式在渲染期间会被系统识别并相应处理。在列表 15 中,我们有 2 个条件区域,条件分别为 comm-empty:yes
和 comm-empty:no
,分别表示评论为空和不为空。
4
模板准备好后,我们只需要调用 ReviewsInfo 作用域上的 ShowConditionArea(..)
来根据条件显示或隐藏区域。看看列表 9 的代码列表第 200-201 行是如何实现的。在第 200 行,我们指示渲染引擎仅当 commCount <= 0
时显示 comm-empty:yes
区域,即当没有评论时。现在想象一下,拥有这个小工具,您的 UI 可以多么灵活。您可以有多个条件区域,将它们嵌套在一起以实现条件 AND,或将它们并排放置以实现条件 OR!就像占位符替换一样,这种操作可以应用于重复内容。另外请记住,DaST 按控制器类中调用的顺序执行模板转换。也就是说,如果您先添加一些占位符替换,然后应用条件区域,那么转换顺序将在渲染过程中保持。使用这个神奇的工具,我们必须记住,条件区域显然不能有嵌套数据作用域。在下一版本框架中,我计划添加更多有用的直接模板转换功能。
9) DaSTUtils 类
我添加了 DaSTUtils
类,其中包含一组实用函数,用于简化一些常见的编程任务。此实用程序用于列表 13 中,以从传递给 UpdatePagerValues(..)
函数的通用对象中检索值。DaSTUtils 类图如图 16 所示。
图 16:DaSTUtils 类。
以下是对函数的简要总结:
GetValue
- 从对象中检索指定名称的公共属性。HasValue
- 检查对象是否具有指定名称的公共属性。ParseJSON
- 解析 JSON 字符串并返回对象图。
未来发展计划
尽管 DaST 渲染引擎的当前版本仍处于 Beta 阶段,但它非常稳定,您可以开始在项目中使用它(显然,不含任何保修——参见版权声明)。我无法保证官方文档的任何内容——很可能我们需要等待几个月。总体的平台架构已经完成,我预计不会有重大的设计更改;但是,我将重构框架的某些部分并为其添加更多功能。DaST 近期最重要的开发总结如下:
- 干净的异常处理、故障排除工具等。
首先需要干净统一的异常处理,并带有有意义的消息以帮助查找问题。我将创建一个新的异常类型,用于抛出所有开发人员错误。我们还需要一种方法来随时查看模型作用域树信息。这对于故障排除和测试非常有用。根据设计,作用域树数据结构不对外公开,这一点不会改变,但我会添加类似
ToXml(..)
方法或类似功能,以便开发人员始终能够看到他们的更改如何影响内部数据结构。 - 更多直接模板操作功能。
除了占位符替换和条件区域之外,我还计划添加重复结果片段部分的能力。这意味着我们可以在数据作用域内部创建重复器并将它们与条件区域混合。此功能极其重要且有用,因为我们可能不想为重复器和网格创建单独的数据作用域。我相信开发人员会真正欣赏 DaST 应用程序中直接模板操作所提供的卓越灵活性。
- 将作用域 DIV 属性转移到初始作用域参数。
我本来打算为当前版本做这个,但只是忘记了。这种情况常有 :) 就像标准 Forms 允许通过在 ASPX 文件中指定控件的属性来参数化控件一样,DaST 框架将允许参数化数据作用域。在模板内部,您将指定带有属性集的作用域 DIV 标签。所有以下划线“_”开头的属性都将成为作用域参数,您可以通过作用域实例访问它们。需要下划线是为了避免作用域参数与标准 HTML 属性重叠。
- 允许各种作用域容器。
我必须先检查所有内容,但对我来说,模板中的作用域容器不只是 DIV 标签,而是所有合适的容器标签(即具有
innerHtml
属性的标签)似乎都没有问题。这是必需的,因为某些应用程序必须严格符合 W3C 标准,例如,DIV 不能放在 SPAN 里面,这在模板中放置 DIV 作用域时会产生一定的限制。允许数据作用域使用各种容器可以完全解决这个问题。 - 从 ASP.NET Ajax 迁移到 jQuery Ajax 实现。
我认为这是涉及重大开发的最重要变化。DaST 渲染引擎中当前的局部更新实现完全基于标准的 ASP.NET Ajax 库。实现本身非常简单,局部更新引擎相当稳定,并且我能够以一种非常优雅的方式将 DaST 局部更新合并到标准的 AJAX 中,这听起来很不错。唯一的问题是微软的客户端 AJAX 库非常庞大,如果您的应用程序使用 DaST 框架,其中 90% 的代码将变得毫无用处。因此,我认为在一个良好的生产版本中,这种开销是不可接受的,必须消除。这基本上意味着将来需要重写整个 AJAX 库以满足 DaST 开发人员的需求。对我来说,一个有吸引力的解决方案是使用 jQuery Ajax 实现,但我不能确定这是最佳解决方案。我也可能最终得到一个不带任何第三方库的纯 JavaScript 实现。
就这样。我邀请大家经常访问DaST 开发网站,关注新闻和框架更新。我们仍在设置开源项目环境,一旦准备好,我将发布相关新闻。我为延迟表示歉意——在过去的几个月里,我在主要工作中一直忙于新的有趣项目。但是,由于所有架构工作都已完成,从现在开始,我将非常频繁地更新框架并添加更多功能。DaST 渲染引擎的正式发布将在几个月后,但当前的 Beta 版本功能齐全且稳定,可供您在项目中使用。