目录

介绍

背景

基本结构

JS控制器

HTML视图

查看容器

结论

参考


  • 下载源3.4 MB
  • Codeplex上下载最新版本和更多示例
  • 参见工作示例

介绍

在上一篇文章中,我演示了如何使用WebAPIJeneva.Net创建基于JSON的Web后端。在本文中,我将展示一些有关如何使用Angular.js创建单个页面前端的基本技巧。如果您不熟悉AngularJS基础知识,请访问此YouTube频道。

背景

假设我们已经有一个工作的后端,它包含两个控制器——一个MVC控制器提供HTML视图,另一个WebAPI控制器为我们提供方便的REST JSON API。

public class ClientController : System.Web.Mvc.Controller
{public ActionResult List(){return this.View();}public ActionResult Create(){return this.View();}public ActionResult Edit(){return this.View();}
}
public class ClientController : System.Web.Http.ApiController
{public IClientService ClientService { get; set; }public Client GetById(int id){return this.ClientService.GetById(id);}public IList<client> GetAll(){return this.ClientService.GetAll();}public void Save(Client item){this.ClientService.Save(item);}public void Update(Client item){this.ClientService.Update(item);}[HttpPost]public void Delete(int id){this.ClientService.Delete(id);}
}

两种控制器看起来都很简单且可行。请参考我以前的文章, 以了解如何创建后端。我的目标是为这两个控制器创建前端。前端必须是单页浏览器应用程序(SPA)。许多人认为SPA不是一种有用的技术,因为它不会产生指向不同页面的链接,并且SPA需要您付出大量的努力,因此不值得。我将向您展示,使用AngularJS,您可以像创建多页应用程序一样简单地创建SPA,并且多页应用程序的所有好处都将保留在SPA中。

基本结构

每个单页应用程序(SPA)的最重要部分是其结构。我们将有3个视图:“客户端列表”,“创建客户端”和“编辑客户端”。这实际上意味着,我们将有三个AngularJS这些视图控制器——clientListController.jsclientCreateController.jsclientEditController.js。“vendor”文件夹包含所有第三方库。我们也有自定义过滤器。通常,这种结构还具有“指令 ”和“服务 ”文件夹,但是在这个简单的SPA中,我没有使用任何自定义指令和服务。

前端的基本部分是位于js文件夹根目录中的app.js文件。让我们分析其内容。

var myclients = myclients || {};
myclients.app = angular.module("myclients", ["ngRoute", "jenevamodule"]);myclients.app.config(function ($routeProvider) {$routeProvider.when("/", {templateUrl: "client/list",controller: "clientListController"}).when("/client/list", {templateUrl: "client/list",controller: "clientListController"}).when("/client/create", {templateUrl: "client/create",controller: "clientCreateController"}).when("/client/edit/:id", {templateUrl: "client/edit",controller: "clientEditController"}).otherwise({templateUrl: "home/notfound"});
});$jeneva.settings.baseUrl = "/api/";

在第一行中,我定义了应用程序的主名称空间。然后,使用AngularJS,创建主模块的实例。这是每个Angular应用程序的常见任务。并且此模块依赖于两个不同的模块:ngRoute 和jenevamodule,它们都是独立的可重复使用的angular模块。最有趣的部分是在中间部分。如您所见,我在这里定义了几个路由器。这实际上是任何前端的核心。简单地说——在这里,我告诉AngularJS-应该打开哪个js控制器和哪个视图,具体取决于浏览器中的URL。每时每刻,我的应用程序只有一个活动视图控制器,这取决于在浏览器窗口中输入的URL。例如,如果我输入url:client/list,则client/list视图和clientListController将被主动加载。

app.js的最后一部分专用于Jeneva.Net。我使用它是因为它使我的应用程序更易于开发和支持。我告诉Jeneva,我的WebAPI控制器位于/api/子文件夹中。

JS控制器

现在,让我们看一下控制器。第一个是clientListController.js

myclients.app.controller("clientListController", ["$scope", "jeneva",function ($scope, jeneva) {$scope.clientRows = new Array();$scope.ClientRow = function (id) {this.id = id;this.name = null;this.lastname = null;this.age = null;this.logins = new Array();};$scope.loadClients = function() {jeneva.get("client/getall").then(function (items) {angular.forEach(items, function (p, i) {var row = new $scope.ClientRow(p.id);row.name = p.name;row.lastname = p.lastname;row.age = p.age;angular.forEach(p.logins, function (q, j) {row.logins.push(q.name);});$scope.clientRows.push(row);});});};$scope.onDelete = function (clientId) {jeneva.post("client/delete/" + clientId).then(function () {$scope.loadClients();});};$scope.$on("$routeChangeSuccess", function () {jeneva.setScope($scope);$scope.loadClients();});
}]);

如您所见,我指的是在app.js文件的前两行中创建的myclients.app模块。我的控制器已命名为clientListController,并注入了2个变量:$scope和jeneva。在控制器主体中,我定义了数据库中的客户端列表$scope.clientRows,这是我的视图模型。该$scope.loadClients方法与后端进行交互,其jeneva使用服务来调用WebAPI控制器。我使用jeneva服务而不是直接使用angular($http)调用的原因是——如果后端失败,并且我在视图中的正确位置(基于jvPath验证器和jvErrorkey指令)显示错误消息,jeneva会自动进行管理。当我的WebAPI返回客户列表,我将其填充到$scope.clientRows字段中。

我的控制器的最后一部分是最重要的。我将处理程序附加到该$routeChangeSuccess事件。每当此控制器处于活动状态时,即每次用户导航到“客户端列表”视图时,都会触发此事件。就我而言,触发此事件时,将调用$scope.loadClients方法,并从后端提取数据。

其他控制器大致相同。它们注入了$location变量,用于在AngularJS SPA中的视图之间导航。例如,clientCreateController.js

myclients.app.controller("clientCreateController",["$scope", "$location", "jeneva", function ($scope, $location, jeneva) {$scope.name = null;$scope.lastname = null;$scope.age = null;$scope.loginRows = new Array();$scope.LoginRow = function () {this.name = null;this.password = null;this.enabled = false;};$scope.onRemoveLoginClick = function (item) {var index = $scope.loginRows.indexOf(item);$scope.loginRows.splice(index, 1);};$scope.onAddLoginClick = function () {var row = new $scope.LoginRow();$scope.loginRows.push(row);};$scope.onSave = function () {var data = {};data.name = $scope.name;data.lastname = $scope.lastname;data.age = $scope.age;data.logins = new Array();angular.forEach($scope.loginRows, function (p, i) {var item = {};item.name = p.name;item.password = p.password;item.enabled = p.enabled;data.logins.push(item);});jeneva.post("client/save", data).then(function () {$location.path("client/list");});};$scope.$on("$routeChangeSuccess", function () {$scope.onAddLoginClick();});
}]);

看一下$scope.onSave方法。当用户单击“客户端创建视图的“保存”按钮时,它将触发。它将所有用户输入的数据收集到一个大数据变量中,并使用jeneva服务将其作为JSON发送到WebAPI控制器。如果成功保存数据,则将调用$location.path()方法,并且用户将再次导航到“客户端列表视图。如果后端失败或后端验证失败,则jeneva服务将处理此问题,并且所有验证问题将显示在视图中的正确位置,具体取决于jeneva指令(本文后面,您将看到Jeneva如何处理这些失败)。

clientEditController.js以同样的方式工作,你可以看到它的源代码。

HTML视图

如您所知,只有三个视图——客户端列表创建客户端编辑客户端。如果您不熟悉AngularJS,请参考此youtube频道。让我们看一下“列表视图。

<a href="#/client/create">NEW CLIENT</a>
<h2>Clients</h2>
<span class="error" jv-error-key></span>
<hr />
<table border="1" style="width: 50%;">
<thead><tr class="head"><th>ID</th><th>NAME</th><th>LASTNAME</th><th>AGE</th><th>LOGINS</th><th></th></tr>
</thead>
<tbody><tr ng-repeat="row in clientRows"><td ng-bind="row.id"></td><td ng-bind="row.name"></td><td ng-bind="row.lastname"></td><td ng-bind="row.age"></td><td><div ng-repeat="login in row.logins"><span ng-bind="login"></span><br /></div></td><td><a ng-href="#/client/edit/{{row.id}}">Edit</a></td><td><input type="button" ng-click="onDelete(row.id)" value="Delete" /></td></tr>
</tbody>
</table>

对于那些熟悉AngularJS的人来说,理解这段代码非常简单。该视图与clientListController.js绑定在一起。基本上,此代码引用控制器代码中的$scope.clientRows字段,并将客户端列表显示为HTML表。

这里最有趣的是链接。看一下链接:

<a href="#/client/create">NEW CLIENT</a>

如您所见,URL以号开头,这实际上意味着,如果您单击链接,浏览器将不会重新加载页面。相反,AngularJS将捕获此事件,并根据在app.js文件中注册的路由切换活动视图和活动控制器。例如,如果您单击NEW CLIENT链接,AngularJS将用Create Client视图的内容替换当前视图的内容,并且活动控制器将变为——clientCreateController.js

另一个有趣的是在视图顶部:

<span class="error" jv-error-key></span>

这由Jeneva的jv-error-key 指令控制span。实际上,这意味着当故障响应从后端服务器到达浏览器时,带有空键的错误消息将显示在span内。有时,您必须使用无密钥(或无路径)错误消息,例如“Unexpected failure”消息或一些更具体的消息——“You cannot delete this client”(或者您可以为某些错误消息提供静态密钥)。关于无密钥(或静态密钥)失败消息的最好的事情是,它们不需要将任何JSON数据发布到服务器。除了无密钥消息外,您还可以随时定义自定义密钥,例如,您可以定义“server_error“密钥(路径),然后可以使用此密钥将失败注册到验证上下文,然后可以使用jv-error-key 伪指令在表单上的任何位置显示该失败消息。在此示例中,我将无密钥消息用于未处理的异常,并且在每个表单的顶部显示它们。我还使用无密钥消息通知用户由于某种原因用户无法删除客户端(请参阅ClientService中的删除客户端)。

现在看一下“编辑客户端”链接:

<a ng-href="#/client/edit/{{row.id}}">Edit</a>

如您所见,此链接导航到“编辑客户端视图,其中还包含客户端的ID。editClientController必须知道如何从链接中提取此ID。看一下app.js文件,如何定义“编辑客户端视图的路由。

.when("/client/edit/:id", {templateUrl: "client/edit",controller: "clientEditController"
})

AngularJS路由机制能够处理url参数。并且clientEditController可以使用$routeParams服务访问它们。$routeParams必须以与其他服务相同的方式将服务注入到控制器中。请查看源代码以获取更多详细信息。

现在,让我们看一下“创建客户端视图。

<a href="#/client/list">ALL CLIENTS</a>|
<h2>New Client</h2>
<span class="error" jv-error-key></span>
<span class="error" jv-error-key="logins"></span>
<hr />
<table ng-form name="form">
<tr><td>Name</td><td><input name="name" type="text" ng-model="name" jv-path="name" /><span class="error" ng-if="form.name.$error.jvpath" ng-repeat="msg in form.name.$jvlist">{{msg}}</span></td>
</tr>
<tr><td>Last name</td><td><input name="lastname" type="text" ng-model="lastname" jv-path="lastname" /><span class="error" ng-if="form.lastname.$error.jvpath" ng-repeat="msg in form.lastname.$jvlist">{{msg}}</span></td>
</tr>
<tr><td>Age</td><td><input name="age" type="text" ng-model="age" jv-path="age" /><span class="error" ng-if="form.age.$error.jvpath" ng-repeat="msg in form.age.$jvlist">{{msg}}</span></td>
</tr>
<tr><td>Logins</td><td><table><thead><tr><th>Login</th><th>Password</th><th>Enabled</th><th></th></tr></thead><tbody><tr ng-repeat="row in loginRows" ng-form name="loginForm"><td><input name="loginName" type="text" ng-model="row.name" jv-path="{{'logins['+ $index + '].name'}}" /><span class="error" ng-if="loginForm.loginName.$error.jvpath" ng-repeat="msg in loginForm.loginName.$jvlist">{{msg}}</span></td><td><input name="password" type="text" ng-model="row.password" jv-path="{{'logins['+ $index + '].password'}}" /><span class="error" ng-if="loginForm.password.$error.jvpath" ng-repeat="msg in loginForm.password.$jvlist">{{msg}}</span></td><td><input name="enabled" type="checkbox" ng-model="row.enabled" jv-path="{{'logins['+ $index + '].enabled'}}" /><span class="error" ng-if="loginForm.enabled.$error.jvpath" ng-repeat="msg in loginForm.enabled.$jvlist">{{msg}}</span></td><td><input type="button" ng-click="onRemoveLoginClick(row)" value="Delete" /></td></tr></tbody></table><div style="padding-bottom: 0.5em;"><input type="button" ng-click="onAddLoginClick()" value="Add" /></div></td>
</tr>
</table>
<hr />
<input type="button" ng-click="onSave()" value="Save" />

每个input元素都使用ng-model Angular指令绑定到相应控制器的$scope字段。每个input元素后都带有错误消息span。

<input name="name" type="text" ng-model="name" jv-path="name" />
<span class="error" ng-if="form.name.$error.jvpath" ng-repeat="msg in form.name.$jvlist">{{msg}}</span>

或像这样:

<tr ng-repeat="row in loginRows" ng-form name="loginForm">
<td><input name="loginName" type="text" ng-model="row.name" jv-path="{{'logins['+ $index + '].name'}}" /><span class="error" ng-if="loginForm.loginName.$error.jvpath" ng-repeat="msg in loginForm.loginName.$jvlist">{{msg}}</span>

如您所见,使用ng-if 和ng-repeat指令修饰的span。这基本上意味着,如果loginForm的name字段验证失败,span则会显示错误消息。第二种情况看起来很奇怪,但是也很简单。jv-path="{{'logins['+ $index + '].name'}}"表示如果logins[0].name字段失败——错误消息将被放置在正确的位置。

查看容器

好吧,就是这样。我们有视图,有控制器,有app.js ,它可以配置所有内容。最后一步是创建一个页面,即单个页面。托管所有视图和控制器的页面。当用户导航到Web应用程序的根目录时,此页面将显示在浏览器中。我的容器页面位于:views/home/Index.cshtml。看起来很简单:

<!DOCTYPE html>
<html ng-app="myclients">
<head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"><title>MyClients Single Page Angular Application</title><script type="text/javascript" src="@Url.Content("/Resources/js/vendor/angular.js")"></script><script type="text/javascript" src="@Url.Content("/Resources/js/vendor/angular-route.js")"></script><script type="text/javascript" src="@Url.Content("/Resources/js/vendor/jeneva.angular.js")"></script><script type="text/javascript" src="@Url.Content("/Resources/js/app.js")"></script><script type="text/javascript" src="@Url.Content("/Resources/js/filters/idtext.js")"></script><script type="text/javascript" src="@Url.Content("/Resources/js/filters/datetime.js")"></script><script type="text/javascript" src="@Url.Content("/Resources/js/controllers/clientCreateController.js")"></script><script type="text/javascript" src="@Url.Content("/Resources/js/controllers/clientListController.js")"></script><script type="text/javascript" src="@Url.Content("/Resources/js/controllers/clientEditController.js")"></script><link href="@Url.Content("~/Resources/css/style.css")" rel="stylesheet"/>
</head>
<body><ng-view></ng-view>
</body>
</html>

首先,我必须使用ng-app指令定义应用程序模块。我的模块称为myclients,这是app.js文件中引用的模块。然后,我必须包含angular.jsjeneva.angular.js以及所有控制器、服务、指令和过滤器。

如您已经猜到的那样,body元素仅包含一个标签——<ng-view>,AngularJS将用当前活动视图替换此标签的内容。就是这样。

结论

使用AngularJS,您可以轻松创建单页应用程序。Jeneva.Net通过管理验证例程使其变得更加简单。

参考

  • 在Codeplex上下载最新版本和更多示例
  • 参见工作示例
  • 文章:带有Upida/Jeneva.Net的ASP.NET MVC单页应用程序(后端)
  • 文章:使用Upida/Jeneva.Net验证传入的JSON
  • Jeneva的Java / Spring版本

带有Upida/Jeneva的ASP.NET MVC单页应用程序(前端/AngularJS)相关推荐

  1. 带有Upida/Jeneva.Net的ASP.NET MVC单页应用程序(后端)

    目录 介绍 问题 问题1 问题2 问题3 解决方案 问题1--智能序列化 问题2--反向引用 问题3--映射更新 说明 下载源3.4 MB 在Codeplex上下载最新版本 参见工作示例 介绍 让我们 ...

  2. 低代码Web应用程序构造方法-ASP.NET Core 2.2单页应用程序(SPA)

    目录 介绍 网格记录在编辑/添加上的持久性 以下步骤用于创建单页应用程序(SPA) 在SQL Server Management Studio中创建数据库 打开Visual Studio社区2019 ...

  3. 如何在单页应用程序Angular 7中使用FastReport Core Web报表

    2019独角兽企业重金招聘Python工程师标准>>> 下载FastReport.Net最新版本 单页应用程序的概念正在寻找越来越多的支持者.最着名的单页框架之一是Angular,它 ...

  4. razor页面跳转_如何在Blazor中使用Razor页面创建单页应用程序

    razor页面跳转 In this article, we are going to create a Single Page Application (SPA) using Razor pages ...

  5. jsp 构建单页应用_如何使用服务器端Blazor构建单页应用程序

    jsp 构建单页应用 介绍 (Introduction) In this article, we will create a Single Page Application (SPA) using s ...

  6. SPA (单页应用程序)

    单页Web应用 编辑 单页Web应用(single page web application,SPA),就是只有一张Web页面的应用.单页应用程序 (SPA) 是加载单个HTML 页面并在用户与应用程 ...

  7. 使用Vanilla.js构建单页应用程序(SPA)网站

    目录 项目 带有模块的组织代码 以可观察的方式应对变化 支持声明式数据绑定 将幻灯片(Slides)托管和加载为"页面" 使用路由器处理导航 带有CSS3动画的转换时间线 管理&q ...

  8. spa 搜索引擎_网站seo-SEO的单页应用程序(SPA)生存指南

    JavaScript库与JavaScript框架 解开SPA背后的技术最终将我们引向JavaScript库和框架的主题. 问一个开发人员"库和框架之间有什么区别",你会得到很多有趣 ...

  9. 可扩展的web单页应用程序架构

    可扩展的web单页应用程序架构 本文转载自:众成翻译 译者:杨小福 链接:http://www.zcfy.cc/article/1319 原文:http://blog.mgechev.com/2016 ...

最新文章

  1. 根据坐标如何标记图片_如何玩转FloodFill算法?
  2. android屏幕密度高度,Android获取常用辅助方法(获取屏幕高度、宽度、密度、通知栏高度、截图)...
  3. JVM---虚拟机栈(动态链接与方法返回地址)
  4. mysql load data 语法_MySql LOAD DATA 使用
  5. android显示布局边界的边距_Android设计规范 Material Design-Layout(2 度量与边框)
  6. websocket原理
  7. oracle 试图访问已经在使用的事物处理临时表,解决ORA-14450:试图访问已经在使用的事务处理临时表-Oracle...
  8. 【codevs2370】小机房的树,RMQ求LCA
  9. ActiveMq工作笔记002---Centos7.3安装ActiveMq
  10. 新功能又来啦!这次是「代码搜索」和视频直播!
  11. Facebook 推出新聊天机器人,号称击败谷歌?
  12. 第三次被盗:Cream Finance 疑存在漏洞,价值1.3亿美元的密币失窃
  13. C#打开php链接传参然后接收返回值
  14. deeplearning.ai——构建循环神经网络
  15. tomcat8修改session的JSESSIONID名称
  16. Python:2行代码实现文字转语音
  17. 【黑金原创教程】【TimeQuest】【第五章】网表质量与外部模型
  18. Neyman-Pearson 奈曼-皮尔逊决策分析
  19. java for循环 等待_在forEach循环中使用异步/等待
  20. 排序:ORDER BY

热门文章

  1. C语言指针实数组输入输出,C语言:回来两个数组中第一个元素的指针,并输出这个值...
  2. mysql核心技术分析_深入理解MySQL核心技术
  3. python列表心得_Python学习心得(第一篇:字符串、列表等)
  4. 创新元旦新年PSD分层海报,新气象开启!
  5. 抓眼球包装设计样机模板,色彩秘籍都在这里了!
  6. C语言以二进制形式读入文件
  7. Linux内存管理:知识点总结(ARM64)
  8. CentOS 7 Linux实时内核下的epoll性能分析
  9. 2019.02.10 17:49
  10. python读取print输出的内容_Python文件中将print的输出内容重定向到变量中