ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 数据点 - 再探 JavaScript 数据绑定(现在使用 Aurelia) 作者 [Julie Lerman](https://msdn.microsoft.com/zh-cn/magazine/mt149362?author=Julie+Lerman) | September 2015 | 获取代码: [C#](http://download.microsoft.com/download/F/A/B/FABA846E-7E6A-499F-AC79-931F1E4EACA3/Code_Lerman.DataPoints.0915.zip)[VB](http://download.microsoft.com/download/F/A/B/FABA846E-7E6A-499F-AC79-931F1E4EACA3/VBCode_Lerman.DataPoints.0915.zip) ![](https://box.kancloud.cn/2016-01-08_568f2a80ad043.jpg) 我对前端开发者的工作从来都不太感兴趣,但时不时会去研究下 UI。我在看到 Knockout.js 上的用户组呈现后,便进行了深入研究,并在我的 2012 年 6 月专栏中,撰写了一篇有关在网站中使用 Knockout 实现 OData 数据绑定的文章 ([msdn.microsoft.com/magazine/jj133816](https://msdn.microsoft.com/magazine/jj133816))。几个月后,我撰写了有关如何将 Breeze.js 添加到组合中,以简化通过 Knockout.js 实现的数据绑定的文章 ([msdn.microsoft.com/magazine/jj863129](https://msdn.microsoft.com/magazine/jj863129))。我在 2014 年撰写了有关如何现代化旧的 ASP.NET 2.0 Web 窗体应用并再次使用 Knockout 的文章,有些朋友取笑我说,Knockout 都是“2012 年的东西了”。 Angular 等较新的框架也可以执行数据绑定及更多任务。但我对“执行更多任务”并不感兴趣,Knockout 足矣。 那么,现在是 2015 年了,尽管对于 JavaScript 数据绑定而言,Knockout 依旧有效、相关且精彩绝伦,但我花了一些时间来研究这些新框架中的一种,并选择了 Aurelia ([Aurelia.io](http://aurelia.io/)),因为我知道有许多 Web 开发者对 Aurelia 很感兴趣。Aurelia 最先由 Rob Eisenberg 推出,他是另一个 JavaScript 客户端框架 Durandal 的缔造者,不过他停止了对 Durandal 的研究,转而去了 Google 的 Angular 团队。最终,他离开了 Angular 并选择从头开始创建 Aurelia,而不是振兴 Durandal。关于 Aurelia,有许多有趣的事情。虽然我还有很多东西要学习(这是当然),但我想与大家分享一些我知道的数据绑定诀窍,以及 EcmaScript 6 (ES6)(JavaScript 的最新版本,并于 2015 年 6 月成为标准)的一点使用技巧。 ## 用于向我的网站提供数据的 ASP.NET Web API 我使用的是自己构建的 ASP.NET Web API,以便公开我使用 Entity Framework 6 持久保存的数据。此 Web API 包含几个可通过 HTTP 调用的简单方法。 Get 方法(如图 1 所示)会接收一些查询和分页参数,并将这些传递到存储库方法,此方法使用 Entity Framework DbContext 检索 Ninja 对象及其相关 Clan 对象的列表。在 Get 方法收到结果后,它会将这些结果转换成一组在其他位置定义的 ViewListNinja 数据传输对象 (DTO)。这是重要的一步,因为 JSON 的序列化方式包含从 Clan 回到其他 Ninjas 的循环引用,这显得有点过度操作。借助 DTO,我可以避免浪费网络数据传输量,并能生成更符合客户端最新动态的结果。 图 1:Web API 中的 Get 方法 ~~~ public IEnumerable<ViewListNinja> Get(string query = "",   int page = 0, int pageSize = 20)   {     var ninjas = _repo.GetQueryableNinjasWithClan(query, page, pageSize);     return ninjas.Select(n => new ViewListNinja                               {                                 ClanName = n.Clan.ClanName,                                 DateOfBirth = n.DateOfBirth,                                 Id = n.Id,                                 Name = n.Name,                                 ServedInOniwaban = n.ServedInOniwaban                               });     } ~~~ 图 2 展示了此方法根据检索了两个 Ninja 对象的查询生成的 JSON 结果视图。 ![](https://box.kancloud.cn/2016-01-08_568f81ec53680.png) 图 2:通过 Web API 的 Ninjas 列表请求生成的 JSON 结果 ## 使用 Aurelia 框架查询 Web API Aurelia 范例将一个视图模型(JavaScript 类)与一个视图(HTML 文件)配对,并在二者之间执行数据绑定。因此,我准备了 ninjas.js 文件和 ninjas.html 文件。Ninjas 视图模型的定义为拥有 Ninjas 数组和 Ninja 对象: ~~~ export class Ninja {   searchEntry = '';   ninjas = [];   ninjaId = '';   ninja = '';   currentPage = 1;   textShowAll = 'Show All';   constructor(http) {     this.http = http;   } ~~~ ninjas.js 中最关键的方法是 retrieveNinjas,它可调用 Web API: ~~~ retrieveNinjas() {   return this.http.createRequest(     "/ninjas/?page=" + this.currentPage +     "&pageSize=100&query=" + this.searchEntry)     .asGet().send().then(response => {       this.ninjas = response.content;     });   } ~~~ 在 Web 应用中的其他地方,我设置了基 URL,以供 Aurelia 查找并合并到请求的 URL 中: ~~~ x.withBaseUrl('http://localhost:46534/api'); ~~~ 值得注意的是,ninjas.js 文件只是 JavaScript。如果您已经使用过 Knockout,您可能记得,您需要使用 Knockout 表示法设置视图模型,以便当对象绑定到标记时,Knockout 将知道该如何处理此对象。不过,这并不适用于 Aurelia。 响应现在包括 Ninjas 列表,我将其设置为我的视图模型的 ninjas 数组,它会返回至触发相应请求的 ninjas.html 页面。用于标识模型的标记中没有任何内容,由于模型与 HTML 配对,因此受到关注。事实上,网页的大部分内容包含的是标准 HTML 和一些 JavaScript,只有少量特殊命令可供 Aurelia 查找和处理。 ## 数据绑定、字符串内插和格式设置 ninjas.html 的最有意思的部分是 div,它用于显示 ninjas 列表: ~~~ <div class="row">   <div  repeat.for="ninja of ninjas">     <a href="#/ninjas/${ninja.Id}" class="btn btn-default btn-sm" >       <span class="glyphicon glyphicon-pencil" />  </a>     <a click.delegate="$parent.deleteView(ninja)" class="btn btn-default btn-sm">       <span class="glyphicon glyphicon-trash" />  </a>     ${ninja.Name}  ${ninja.ServedInOniwaban ? '[Oniwaban]':''}     Birthdate:${ninja.DateOfBirth | dateFormat}   </div> </div> ~~~ 此代码中的第一个 Aurelia 标记是 repeat.for"ninja of ninjas",其遵循 ES6 范例进行循环。由于 Aurelia 包括视图模型,因此它知晓“ninjas”是定义为数组的属性。可以对变量“ninja”进行任意命名,例如“foo”。 它只是表示 ninjas 数组中的每一项。现在,这只关乎遍历 ninjas 数组中的项。向下跳至显示这些属性的标记,例如“${ninja.Name}”。 这是 Aurelia 利用的 ES6 功能,称为字符串内插。字符串内插通过将变量嵌入其中(而不是通过其他方式,例如,连接),让字符串的编写变得更加容易。因此,对于变量名称“Julie”,我可以用 JavaScript 进行编写: ~~~ `Hi, ${name}!` ~~~ 它会作为“Hi, Julie!”得到处理。 Aurelia 会利用 ES6 字符串内插,并在遇到相应语法时推断单向数据绑定。因此,以 ${ninja.Name} 开头的最后一行代码将输出 ninja 属性,以及 HTML 文本的其余部分。如果您是用 C# 或 Visual Basic 编写代码,请注意,字符串内插是 C# 6.0 和 Visual Basic 14 的新功能。 在此期间,我确实要多了解一点 JavaScript 语法,例如,ServedInOniwaban 布尔的条件评估,它具有与 C# 相同的语法 (condition? true : false)。 我应用到 DateOfBirth 属性的日期格式是另一项 Aurelia 功能,如果您使用的是 XAML,则可能会感到熟悉。Aurelia 使用值转换器。我喜欢使用目前的 JavaScript 库来帮助完成日期和时间格式设置,并在 date-format.js 类中利用这一功能: ~~~ import moment from 'moment'; export class dateFormatValueConverter {   toView(value) {   return moment(value).format('M/D/YYYY');   } } ~~~ 请注意,在类名称中,您确实需要使用“ValueConverter”。 在 HTML 页面的顶部(在初始 元素的正下方),我对相应文件进行了引用: ~~~ <template>   <require from="./date-format"></require> ~~~ 现在,字符串内插能够在我的标记中找到 dateFormat­[ValueConverter],并将它应用于输出,如图 3 所示。 ![](https://box.kancloud.cn/2016-01-08_568f81ec6151c.png) 图 3:显示所有 Ninjas,其中包含通过字符串内插由 Aurelia 实现单向绑定的属性 我想指出的是 div 中的另一个绑定实例,但它是事件绑定,而不是数据绑定。请注意,在第一个超链接标记中,我使用了常见语法,同时在 href 特性中嵌入了 URL。不过,在第二个标记中,我没有使用 href。相反,我使用了 click.delegate。虽然 Delegate 不是新命令,但 Aurelia 却按特殊方式对其进行处理,这种方式要比标准的 onclick 事件处理程序强大得多。若要了解详情,请访问 [bit.ly/1Jvj38Z](http://bit.ly/1Jvj38Z)。我将继续侧重介绍与数据相关的绑定。 编辑图标会将您引导至包含 ninja 的 ID 的 URL。我已经指示 Aurelia 路由机制路由至 Edit.html 网页。这与 Edit.js 类中的视图模型绑定。 Edit.js 的最关键方法用于检索和保存选定的 ninja。让我们从 retrieveNinja 开始: ~~~ retrieveNinja(id) {   return this.http.createRequest("/ninjas/" + id)     .asGet().send().then(response => {       this.ninja = response.content;     });   } ~~~ 和先前一样,这会生成对我的 Web API 的类似请求,但这一次请求中追加有 ID。 在 ninjas.js 类中,我将结果绑定至我的视图模型的 ninjas 数组属性。此时,我要将这些结果(单个对象)设置为当前视图模型的 ninja 属性。 下面是由于追加到 URI 的 ID 而会被调用的 Web API 方法: ~~~ public Ninja Get(int id)   {     return _repo.GetNinjaWithEquipmentAndClan(id);   } ~~~ 此方法生成的结果要比针对 ninja 列表返回的结果丰富得多。图 4 展示了通过其中一个请求返回的 JSON。 ![](https://box.kancloud.cn/2016-01-08_568f81ec73529.png) 图 4:一个 Ninja 的 WebAPI 请求的 JSON 结果 在我将这些结果推送到我的视图模型中之后,我便可以将 ninja 的属性绑定至 HTML 页面中的元素。这次,我使用的是 .bind 命令。Aurelia 将推断是应该单向绑定,还是双向绑定,亦或是通过其他某种方式绑定。实际上,如您在 ninjas.html 中所见,它会在发现字符串内插时使用其基础绑定工作流。在这种情况下,它使用了一次性单向绑定。此时,因为我使用的是 .bind 命令并将绑定至输入元素,所以 Aurelia 推断我需要的是双向绑定。这是它的默认选择,我可以使用 .one-way 或其他命令来替代 .bind 命令,以此来覆盖此选项。 为了简洁起见,我将只提取相关的标记,而不包括四周的元素。 下面是一个输入元素,它绑定到了模型中 ninja 属性的 Name 属性,该模型是我从 modelview 类传回的: ~~~ <input value.bind="ninja.Name" /> ~~~ 下面是另一个输入元素,这一次它绑定到了 DateOfBirth 字段。我希望能利用我之前学到的语法,轻松地重复使用日期格式值转换器,即使是在这样的上下文中: ~~~ <input  value.bind="ninja.DateOfBirth | dateFormat"  /> ~~~ 我还想在同一网页上列出设备(方法类似于我是如何列出 ninjas 的),以便可以对其进行编辑和删除。为了方便本文演示,我甚至将此列表显示为字符串,但我尚未实现编辑和删除功能,也并未实现设备添加功能: ~~~ <div repeat.for="equip of ninja.EquipmentOwned">   ${equip.Name} ${equip.Type} </div> ~~~ 图 5 展示了具有数据绑定的窗体。 ![](https://box.kancloud.cn/2016-01-08_568f81ec876c8.png) 图 5:显示设备列表的编辑页面 Aurelia 还具有一项名为自适应绑定的功能,可允许其根据浏览器的功能或传入的对象调整绑定功能。这个设计太酷了,即便浏览器和库随着时间的推移而不断演进,它也始终可以与二者配合使用。有关自适应绑定的详细信息,请访问 [bit.ly/1GhDCDB](http://bit.ly/1GhDCDB)。 目前,您只能编辑 ninja 名称、出生日期和 Oniwaban 指示器。当用户取消选中“在 Oniwaban 中提供”并单击“保存”按钮后,此操作会调用我的视图模型的 save 方法。在我将数据推送回我的 Web API 之前,此方法会执行些有趣的操作。目前,如您在图 4 中所见,ninja 对象是一个深层图形。我无需发送回所有内容进行保存,只需发送回相关属性。由于我在另一端使用的是 EF,因此我想确保我没有编辑的属性也会返回,以便其不会被数据库中的 null 值取代。所以,我要创建一个名为 ninjaRoot 的动态 DTO。我已经将 ninjaRoot 声明为我的视图模型的属性。不过,ninjaRoot 的定义将通过我在 Save 方法中生成它的方式予以表明(见图 6)。我一直很小心地使用我的 WebAPI 预期的相同属性名称和大小写,以便其可以将这些内容反序列化为 API 中的已知 Ninja 类型。 图 6:编辑模型视图中的 Save 方法 ~~~ save() {         this.ninjaRoot = {           Id: this.ninja.Id,           ServedInOniwaban: this.ninja.ServedInOniwaban,           ClanId: this.ninja.ClanId,           Name: this.ninja.Name,           DateOfBirth: this.ninja.DateOfBirth,           DateCreated: this.ninja.DateCreated,           DateModified: this.ninja.DateModified         };         this.http.createRequest("/ninjas/")           .asPost()           .withHeader('Content-Type', 'application/json; charset=utf-8')           .withContent(this.ninjaRoot).send()           .then(response => {             this.myRouter.navigate('ninjas');           }).catch(err => {             console.log(err);           });     } ~~~ 请注意此调用中的“asPost”方法。这可确保将请求转到我的 Web API 中的 Post 方法: ~~~ public void Post([FromBody] object ninja) {   var asNinja =     JsonConvert.DeserializeObject<Ninja>     (ninja.ToString());   _repo.SaveUpdatedNinja(asNinja); } ~~~ JSON 对象反序列化为本地 Ninja 对象,然后传递给我的存储库方法,此方法知道对数据库中的这一对象进行更新。 当我在我的网站上返回到 Ninjas 列表后,更改也会反映在输出中。 ## 不仅仅是数据绑定 请注意,Aurelia 是一个功能更加丰富的框架(不仅仅只有数据绑定),但出于我的本性,我在探究此工具的初期阶段将重点放在了数据上。您可以通过 Aurelia 网站了解到更多信息,而[gitter.im/Aurelia/Discuss](http://gitter.im/Aurelia/Discuss) 则是一个活跃的社区。 我要衷心感谢 tutaurelia.net 网站,上面的系列博客探讨了如何将 ASP.NET Web API 和 Aurelia 结合使用。依赖于作者 Bart Van Hoey 的第 6 部分,我第一次显示了 ninjas 列表。可能在本文发表时,这一系列中会新增更多博文。 本文的下载内容包括 Web API 解决方案和 Aurelia 网站。您可以在 Visual Studio 中使用 Web API 解决方案。我已使用 Entity Framework 6 和 Microsoft .NET Framework 4.6 在 Visual Studio 2015 中构建了网站。如果您想运行此网站,则需要访问 [Aurelia.io](http://aurelia.io/) 站点,了解如何安装 Aurelia 和运行此网站。您还可以在我的 Pluralsight 课程“Entity Framework 6 入门”([bit.ly/PS EF6Start](http://bit.ly/PS-EF6Start)) 中,观看我对此应用程序的演示。 * * * Julie Lerman *是 Microsoft MVP、.NET 导师和顾问,住在佛蒙特州的山区。您可以在全球的用户组和会议中看到她对数据访问和其他 .NET 主题的演示。她是《Programming Entity Framework》(2010) 以及“代码优先”版 (2011) 和 DbContext 版 (2012)(均出自 O’Reilly Media)的作者,博客网址为[thedatafarm.com/blog](http://thedatafarm.com/blog)。通过她的 Twitter(网址为 [twitter.com/julielerman](http://twitter.com/julielerman))关注她,并在[juliel.me/PS-Videos](http://juliel.me/PS-Videos) 上观看其 Pluralsight 课程。*