# 数据点 - 再探 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 课程。*
- 介绍
- 云连接移动应用 - 借助身份验证和离线支持构建 Xamarin 应用
- 崛起 - 自由 Internet 广播
- Microsoft Azure - 云中的容错问题和解决方法
- 最前沿 - 适合常见应用程序的事件源
- Azure 深入了解 - 跨云平台创建统一的 Heroku 式工作流
- 借助 C++ 进行 Windows 开发 - Windows 运行时中的高级类型
- 编译器优化 - 借助按本机配置优化来简化代码
- 数据点 - 再探 JavaScript 数据绑定(现在包含 Aurelia)
- 云安全 - 借助 Azure 密钥保管库保护敏感信息的安全
- 测试运行 - 借助人工尖峰神经元进行计算
- 开发运营 - 在 Microsoft 堆栈上启用开发运营
- 孜孜不倦的程序员 - 如何成为 MEAN: Node.js
- 新型应用 - 提升新型应用的易用性的做法
- 别让我打开话匣子 - Darwin 的照相机
- 编辑寄语 - 汽车 Internet 发生故障