# 五、高级编程原理

## 穿越航行边界。

浏览客户屏幕格式化后，是时候将我们的注意力转向添加编辑客户屏幕了。

第一个任务是将屏幕从现在的小弹出窗口转换为占据全部可用高度和宽度的屏幕。打开屏幕，在可视化树中选择根节点，然后从属性窗口中清除“显示为对话框”复选框。

![](img/image082.png)

图 67:选择了屏幕根元素的属性窗口

除了外观属性，您还会发现一个名为“屏幕类型”的行为属性，用于指示屏幕是编辑屏幕还是浏览屏幕。

LightSwitch 使用了一种称为*工作单元*的设计模式，这意味着更改被累积到单个事务中，然后被发送到服务器进行整体验证和处理。每次您从一个编辑导航到另一个屏幕时，当前工作单元将隐式传递到该屏幕，并且在该屏幕中所做的任何更改都将与当前更改一起提交或丢弃。

但是，当您从一个浏览屏幕导航到下一个时，新的工作单元将开始。这意味着，如果有任何未决的更改，LightSwitch 将显示一个弹出窗口，要求最终用户在发出警告之前提交或放弃当前的更改，或者取消导航并一起留在页面上。

![](img/image083.jpg)

图 68:未保存的更改对话框

作为开发人员，您也可以显式关闭一个工作单元。从任何 JavaScript 函数中，这都可以通过调用全局 myApp 对象上的三种方法之一来完成:

![](img/image084.png)

代码清单 16

第一个函数，applyChanges()，将首先尝试验证挂起的更改，当它们有效时，启动一个请求来异步处理服务器上的工作单元。

CommitChanges()也做了同样的事情，但是在服务器做出肯定的响应后，它将关闭当前屏幕并导航回上一个屏幕。CancelChanges()还异步地向后导航，丢弃打开的工作单元。

您可以从屏幕设计器中执行的每个操作也可以从 JavaScript 中调用，甚至更多，通常是通过调用全局 myapp 对象上的函数。举个例子，下面的代码片段演示了如何从代码导航到添加-编辑客户屏幕:

![](img/image085.png)

代码清单 17

当您从集成开发环境创建屏幕时，在全局 myapp 对象上生成了这个 showAddEditCustomer 方法。作为一个参数，它需要一个客户实体。要打开屏幕编辑新的客户实体，您可以考虑编写如下代码:

![](img/image086.png)

代码清单 18

但是，如果您真的尝试从浏览客户屏幕上的按钮运行这段代码，您会遇到一些有趣的行为。只要您单击执行此代码的按钮，LightSwitch 就会提示您，在导航到添加-编辑客户屏幕之前，您需要保存或放弃挂起的更改。

实际发生的情况是，客户实体的构造函数在属于浏览客户屏幕的工作单元的边界内被调用。因为您正在从一个屏幕移动到另一个屏幕，工作单元需要被完成或丢弃，从而导致保存或取消的提示。

幸运的是，showAddEditCustomer 方法接受第二个参数，该参数允许您添加一个将在屏幕显示前执行的回调函数，以及一个在屏幕关闭后执行的回调函数:

![](img/image087.jpg)

图 69:导航到屏幕时传递回调函数

阅读 JavaScript 上的 IntelliSense 会立即发现这个难题缺失的部分:beforeShown 回调函数是在添加-编辑客户屏幕的边界内执行的。要打开屏幕编辑新的客户实体，您需要编写:

![](img/image088.png)

代码清单 19

beforeShown 回调函数主要用于执行特定于扫描程序的初始化代码；afterClosed 回调函数对于在详细信息屏幕完成其工作单元并关闭后执行任何操作都很有用，这甚至可能取决于用户如何离开屏幕(工作单元是已提交还是已取消)，例如:

![](img/image089.png)

代码清单 20

## 初始化值

初始化值通常在三个不同的位置完成:在创建实体时触发的创建事件中，从代码导航到屏幕时使用 beforeShown 回调函数，或者在创建屏幕时触发的创建事件中，按此顺序。

要初始化特定实体上的值，您需要在实体设计器中打开该实体，在底部选择 HTMLClient 的透视图，然后单击顶部的“编写代码”，并选择创建的事件来生成函数存根。

![](img/image090.png)

图 70:实体的写代码按钮

假设平均来说，一个客户一开始是一个快乐的客户，结果假设满意度得分为 80%。您可以在实体设计器生成的 created()函数存根中的应用中对此进行建模:

![](img/image091.png)

代码清单 21

但是，如果需要，您仍然可以将每个屏幕的该值初始化为不同的值。

通过屏幕设计器中的“编写代码”按钮，每个屏幕都有一个类似的选项来生成函数存根，该函数存根在屏幕首次创建时执行。

![](img/image092.png)

图 71:屏幕的写代码按钮

该函数可用于对屏幕上的任何属性、值或实体进行常规初始化。您可以使用此函数存根来测试正在编辑的客户是新的还是现有的实体，然后将屏幕标题从默认的添加-编辑客户设置为更合适的名称:

![](img/image093.png)

代码清单 22

此时，业务规则可以规定，通过此屏幕创建的任何新客户都必须以 90%的基本满意度分数开始:

![](img/image094.png)

代码清单 23

请记住，如果屏幕是从代码中打开的，并且您指定了这样一个方法，那么这段代码将在 beforeShown 回调之后运行。

## 异步编程

我们为添加-编辑客户屏幕编写的初始化代码非常简单:设置屏幕的显示名称并初始化客户实体的满意度得分。我们没有触及任何可能需要一段时间才能完成的属性或字段。当你这样做的时候，为了防止冗长的代码执行时接口冻结，LightSwitch 会强制你使用 WinJS“promise”对象编写异步 JavaScript 代码。作为练习，我们将尝试在添加-编辑客户屏幕上显示一条消息，显示客户是否有任何未结订单。

这当然意味着我们将通过引入一个名为 Order 的新实体来扩展应用，订单和客户之间是一对多的关系。

![](img/image095.jpg)

图 72:订单实体

我还使用前一章中添加的相同的 SQL 后期部署脚本添加了数百个订单实体。

你可以从[https://gist.github.com/janvanderhaegen/6682208](https://gist.github.com/janvanderhaegen/6682208)下载完整的脚本自己获取样本数据。

![](img/image096.png)

代码清单 24

如果一切顺利，您现在可以再次在屏幕设计器中打开添加-编辑客户屏幕，并在视图模型(左侧)上找到客户实体的新链接，将客户订单添加到屏幕视图模型中。

![](img/image097.png)

图 73:您可以通过添加订单将客户的订单添加到屏幕上。

单击“添加订单”后，LightSwitch 将向名为“订单”的屏幕视图模型添加一个新的 VisualCollection。

![](img/image098.png)

图 74:订单可视化集合和客户实体

单击编辑查询链接，在查询设计器中打开订单，并对其进行调整，以仅检索尚未完成的订单。

![](img/image099.jpg)

图 75:仅通过查询设计器检索未结订单

将 orders 集合重命名为 OpenOrders。

现在，应用已经有了订单的概念，并且您已经在屏幕上添加了示例数据和未完成订单的集合，如果客户当前有任何未完成的订单，您已经准备好显示一条消息。

为了能够显示这个消息，我们需要将它存储在视图模型的某个地方。通过单击屏幕设计器顶部的“添加数据项”按钮，并在“添加数据项”向导中创建 OpenOrderMessage 属性，向视图模型添加新的字符串属性。

![](img/image100.jpg)

图 76:添加数据项向导允许您添加本地属性。

从左侧的视图模型中，将这个新的字符串属性拖到视图的某个位置。

![](img/image101.png)

图 77:视图模型和视图中的 OpenOrderMessage 属性

最后，我们需要在 OpenOrderMessage 属性中存储一条消息。您可以从屏幕的 created 函数中进行操作，该函数目前类似于:

![](img/image102.png)

代码清单 25

屏幕的视图模型有一个客户和 OpenOrders 和 OpenOrderMessage 属性。但是，JavaScript 智能感知只显示客户实体和 OpenOrderMessage 字符串，以便在 JavaScript 中用作属性。

![](img/image103.png)

图 78:为 OpenOrderMessage 生成了一个 JavaScript 属性，但没有为 OpenOrders 生成。

生成了一个名为 getOpenOrders 的函数，它返回一个 WinJS promise 对象，而不是 OpenOrders 属性。

![](img/image104.png)

图 getOpenOrders 函数

这种区别背后的想法是，LightSwitch 知道一个简单的属性和一个更复杂的属性之间的区别，前者可以快速访问，因此可以同步完成，而后者像这个集合一样，可能根本没有更新或加载。

确保集合是最新的所需服务调用可能需要一段时间。因此，LightSwitch 强制您检索一个承诺对象，而不是直接使用集合。

promise 对象只不过是对获取数据的引用。IntelliSense 揭示了它可以与许多函数链接，其中，then()函数是您最常用的一个。

![](img/image105.jpg)

图 80:承诺对象上可用的回调函数

这个 then()函数允许您链接一个回调函数，该函数在集合完成加载时执行，或者如果集合已经加载并且是最新的，则立即执行。

![](img/image106.png)

代码清单 26

无论哪种方式，一旦回调函数被执行，LightSwitch 都会确保集合是最新的，并将它作为参数传递给回调函数。添加-编辑屏幕的完全初始化因此变成:

![](img/image107.png)

代码清单 27

或者，因为一百万个像素说一千多个字，在运行时这变成:

![](img/image108.jpg)

图 81:正确初始化的详细屏幕

![](img/image109.png)注意:认为这是多线程应用是不正确的。即使这些技术被明确地称为异步，JavaScript 确实提供了多线程支持(根据规格:[http://www . ECMA-international . org/publications/files/ECMA-ST/Ecma-262 . pdf](http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf))。promise 对象不像真正的多线程应用那样同时运行多个代码路径，而是简单地封装改变一个单线程顺序代码路径的逻辑。

如果您有一个可能需要一段时间才能完成的自定义 JavaScript 函数，LightSwitch 有一个简单的 API 调用，允许您自己将任何函数包装在一个承诺中，就像 Michael Washington 在下面的自描述代码片段中演示的那样，该代码片段摘自“在 Visual Studio LightSwitch 中使用承诺”([http://light witchhelp 网站. com/Blog/tabid/61/EntryId/170/Using-Promises-In-Visual Studio-light switch . aspx](http://lightswitchhelpwebsite.com/Blog/tabid/61/EntryId/170/Using-Promises-In-Visual-Studio-LightSwitch.aspx)):

![](img/image110.png)

代码清单 28

## 服务器端编程

如果至少有一条记录，为了显示它，我们获取了整个集合(实际上是前 45 条，由于分页)。真是浪费用户的时间。我们需要一种更有效的编程方式。

对于这些特定的数据查询，或者为更具体的业务操作建模，LightSwitch 会在服务器项目中生成一个广泛的模型。这样，您可以通过添加自己的 WCF 网络服务、信号中枢或 ASP.NET 网络应用编程接口，以高效的方式完成您的高级场景。

作为一个例子，让我们用最后一个例子来总结任何给定客户每年的订单历史。

如果你对 ASP.NET Web API 不熟悉，我建议[http://www . ASP . net/Web-API/overview/入门-aspnet-Web-API/tutorial-your-first-Web-API](http://www.asp.net/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api)。这里简单总结一下。ASP.Net 网络应用编程接口是一个服务器端框架，对于任何网络请求，服务器都匹配 URI 和 HTTP 动词(POST、GET 等)。)来确定哪个控制器应该被实例化，以及应该调用那个控制器上的哪个方法来处理请求。

添加 ASP.NET 网络应用编程接口所需的引用和设置是通过添加控制器自动处理的，因此首先右键单击服务器项目，然后从上下文菜单中选择添加新文件夹。命名文件夹控制器。通过右键单击文件夹并从添加新项目对话框中选择网络应用编程接口控制器来添加控制器。

![](img/image111.jpg)

图 82:添加网络应用编程接口控制器

添加的空控制器看起来像:

![](img/image112.png)

代码清单 29

在实现代码之前，您可以为此控制器配置路由，以便快速测试。右键单击服务器项目，并从上下文菜单中选择“添加新项”。

从“添加新项”对话框添加新的全局应用类，并用以下代码替换内容:

![](img/image113.png)

代码清单 30

代码将添加一个名为 ReportsAPI 的新路由，该路由将通过初始化正确的控制器并执行一个名为 GET()的方法来处理对该 URL 的所有 HTTP GET 请求，该方法接受一个名为 id 的参数。

![](img/image114.png)

代码清单 31

因为您可以从浏览器执行 HTTP GET，所以您可以通过在 CustomerOrderSummaryController 的 GET 方法中设置断点并按 F5 构建和开始调试来测试您的设置。将您的浏览器从 HTML 客户端的默认 URL(http://localhost:10355/HTML client/)导航到 http://localhost:10355/reports/customerrodersummary/5。您应该到达断点，注意 id 参数的值是 5。端口号 10355 可能在您的系统上有所不同。

下一步是实现控制器，与应用的服务器端 LightSwitch 模型进行交互。这是通过添加对微软。LightSwitch 命名空间，其中 LightSwitch 为类顶部的 using 语句生成了一个名为 ServerApplicationContext 的类。ServerApplicationContext 是 LightSwitch 中的一个新类，从 VS 2012 更新 3 开始。

ServerApplicationContext 类的实例实现了 IDisposable，因此它们应该被包装在一个 using 语句中，以便正确地处置资源，比如与数据库的连接。

![](img/image115.png)

代码清单 32

此上下文公开了允许访问当前经过身份验证的用户及其权限以及访问在实体设计器中创建的实体的方法。您可以直接与实体交互，甚至可以使用客户端上没有的一些扩展方法，比如 GroupBy 方法。

![](img/image109.png)请记住，这个 GroupBy 方法在服务器上始终可用，但是这个方法的成功取决于数据源的类型。您可以围绕现有的 SharePoint 列表或 OData web 服务对 LightSwitch 实体进行建模，在这种情况下，分组将会失败，因为 LightSwitch 与数据源交互的基础协议不支持分组。此外，如果数据源是新的或现有的 SQL 或 SQL Azure 数据库，分组实际上将被推迟到由 SQL 服务器本身完成。

根据给定的客户标识获取和汇总订单历史的完整代码如下:

![](img/image116.png)

代码清单 33

从 GET 操作中，您将从代码中返回一个隐式类型化的 CRL 对象数组。事实上，ASP.NET 网络应用编程接口会自动将数据整理成 JavaScript 对象符号(JSON)格式，这是编程语言不可知的。

你可以从任何编程语言中调用它。事实上，要测试结果，请随意按 Ctrl+F5 来构建和启动应用(无需调试)，然后导航到像 http://localhost:{ yoursportnumber }/reports/customerrodersummary/5 这样的硬编码 URL 来查看结果。根据您的浏览器，该 JSON 响应可能会直接显示或作为文件下载。

![](img/image117.jpg)

图 83:互联网浏览器提供下载订单历史摘要。

## jQuery UI 小部件:JavaScript 的基本工作室

既然您有了在服务器上积累数据的有效方法，那么您就需要一种有效的方法来可视化结果。虽然您可以使用 render 方法自己编写自定义控件，但有趣的是，LightSwitch 绝不会阻止使用第三方库。这是一个测试 Syncfusions 新的 JavaScript 基本工作室的好机会。

本质 JS 是一个新的 JavaScript 框架设计器，用于业务线(LOB)应用。向您的 LightSwitch HTML 应用添加第三方组件总是一个三步走的过程。第一步是添加所需的代码(。js 文件)和样式(。css 文件)移动到 HTMLClient 项目中的适当文件夹。您需要确保通过从网页中引用它们来加载它们，然后您可以在需要的地方使用它们。

下载基本 JS 套件后，复制名为 bootstrap 的样式表。CSS，默认。CSS 和默认响应。CSS 从安装目录移动到 HTMLClient 项目的 Content 子文件夹，并将 ej.widgets.all.min.js 和 properties.js 移动到 Scripts 子文件夹。向应用中添加第三方脚本时，请始终搜索缩小版本，您可以通过。文件名中的 min，以减小下载大小。

接下来，您需要将这些 CSS 和 JavaScript 资源加载到名为 default.htm 的 HTML 页面中。

![](img/image118.png)

图 84:Default.htm，这个 SPA 中的单个 HTML 页面

这个 HTML 页面是一个空壳，由指向所需样式表的链接、显示加载动画的 HTML DIV 元素、指向所需 JavaScript 文件的链接以及最后一个 JavaScript 块组成:

![](img/image119.png)

代码清单 34

此块将导致加载 Microsoft light switch(msls)Javascript 库并启动应用，用构成应用的元素替换加载动画中的 HTML DIV 元素。

在这个网页中，找到指向样式表的链接，并在底部添加指向您刚刚添加到项目中的基本 JS 样式表的链接:

![](img/image120.png)

代码清单 35

接下来，找到指向 JavaScript 文件的链接，并添加指向基本 JS JavaScript 文件的链接:

![](img/image121.png)

代码清单 36

当添加第三方 JavaScript 时，在末尾添加引用是一个很好的做法，但就在 generatedAssets 导入的上方:

![](img/image122.png)

代码清单 37

最后，为了避免重复冲突，您必须注释掉第一个脚本引用:

![](img/image123.png)

代码清单 38

现在，所需的基本 JS 文件已经添加到项目中并加载到 HTML 页面中，您可以在应用中将它们用作自定义控件。

首先，在屏幕设计器中打开添加-编辑客户屏幕，并创建一个名为 OrderSummary 的新选项卡。在此选项卡上，将客户的 id 属性从视图模型拖到视图上，并使用自定义控件来呈现它。在“属性”窗口中，将标签位置设置为“无”。

![](img/image124.png)

图 85:向屏幕添加新标签

在“属性”窗口中，单击“编辑渲染代码”链接，像前面一样生成一个函数存根。同样，您将首先编写一些代码，在 HTML 页面中插入一个 DIV 元素，然后使用回调函数将数据绑定到值(客户标识):

![](img/image125.png)

代码清单 39

在该回调函数中，您将使用一个 jQuery 选择器，通过传递一个散列标签和 ID，将 jQuery 对象包装在刚刚插入的 DIV 元素周围:

![](img/image126.png)

代码清单 40

几乎所有的 JavaScript 库都将被包装在一个 jQuery UI 小部件中。这意味着 JavaScript 库的生产者使用专门的 jQuery 函数向 jQuery 的插件系统注册了 HTML 控件。这样做的好处是生产者可以直接向 jQuery 对象添加功能。例如，这是如何将容器标识为的 DIV 转换为基本 JS 图表:

![](img/image127.png)

代码清单 41

显然，您需要向这个初始化方法传递额外的参数。最重要的是设置数据源。您将指示这个基本的 JS 图表直接从您在前一章中创建的 ASP.NET Web API 控制器中获取它的数据:

![](img/image128.png)

代码清单 42

完整的代码包括附加的格式和颜色:

![](img/image129.png)

代码清单 43

保存您的进度并刷新您的浏览器以查看运行结果:任何客户的添加-编辑屏幕现在都有两个选项卡。单击标题打开订单摘要选项卡，该选项卡将显示 ASP.NET 网络应用编程接口提供的数据的图形。

![](img/image130.jpg)

图 LightSwitch 应用中运行的基本 JS

运行应用时，您会看到一个美丽的动画在运行。

## jQuery 用户界面小部件:自定义必应地图控件

如果在本章结束时没有演示如何利用一些特定于设备的功能(如地理定位)，那将是一种耻辱。地理位置是用于表示 JavaScript 内置函数的名称，该函数请求关于当前位置、高度或速度的信息。这是一个 JavaScript API，所以它兼容每一个移动设备。JavaScript 引擎实际上会询问操作系统，操作系统会首先询问用户的许可。

为了测试这一点，我们将添加一些功能，帮助用户从当前位置导航到为当前客户列出的地址。

首先，在添加-编辑客户屏幕中添加一个名为"地址"的新标签，然后在命令栏中添加一个新命令。

![](img/image131.png)

图 87:从添加按钮向导生成自定义方法

生成一个名为 HowDoIGetThere 的自定义方法。这将在视图中的选项卡命令栏中生成按钮，并作为视图模型中的相应命令。

从属性窗口中选择新添加的命令，并将图标更改为问题。然后，右键单击该命令并选择编辑执行代码选项。

![](img/image132.png)

图 88:自定义命令的上下文菜单

用以下代码替换生成的方法存根:

![](img/image133.png)

代码清单 44

代码将检查是否支持地理定位，然后询问当前位置(这需要最终用户的许可)，并在消息框中显示。

我们将显示一张地图，其中包含如何使用另一个 jQuery UI 小部件联系客户的详细说明，而不是在消息中显示当前坐标:[https://gist.github.com/janvanderhaegen/6310722](https://gist.github.com/janvanderhaegen/6310722)。

![](img/image134.png)这个小部件本身是基于 LightSwitch 程序经理海因里希·温德尔的一篇帖子:http://blogs . msdn . com/b/light switch/archive/2013/01/14/visualizing-list-data-use-a-map-control-Heinrich-wendel . aspx。

下载小部件后，确保将其添加到项目中，并在 default.htm 页面中引用它。再次打开添加-编辑客户屏幕，从屏幕设计器的左侧将客户拖到视图上。

选择“自定义控件”作为要使用的控件，并将标签位置再次设置为“无”。

![](img/image135.png)

图 89:准备一个点来绘制阿炳地图控件

渲染代码添加了两个 HTML DIV 元素；一个用于显示必应地图，一个用于显示方向。然后，它指示小部件绘制阿炳地图并突出显示当前客户的地址。

![](img/image136.png)

代码清单 45

更改 HowDoIGetThere 函数的执行函数后面的代码，使用 jQuery 查找包含地图的 DIV 元素，并绘制当前位置和客户地址之间的路线:

![](img/image137.png)

代码清单 46

保存并刷新浏览器，以查看添加-编辑客户屏幕确实有一张显示当前客户位置的地图，以及“我如何到达那里”按钮。该按钮将在地图上显示最终用户如何从其当前位置到达特定客户的说明。

![](img/image138.jpg)

图 90:必应地图控件将导航指令显示到客户地址