使用 TypeScript、AngularJS、RequireJS、AngularJS-BootStrap UI、WebAPI、Entity Framework 的 ASP.NET 应用程序。






4.83/5 (24投票s)
本文提供了使用 AngularJS、RequireJS 和 TypeScript 以及 AMD 模块系统创建 ASP.Net 应用程序模板的简单步骤。
引言
这是一篇关于一个允许共享资源的应用程序的全栈开发的文章。我所说的资源是指大多数可用的 MIME 类型的文件以及要共享的文章或观点。它使用了包括 TypeScript、RequireJS、AMD、AngularJS 等在内的各种客户端和服务器开发工具和技术。
背景
这篇文章是我在寻找类似技术内容但未找到结果后产生的。我真诚地希望,在阅读完这篇文章后,您能够组合最少量的 AngularJS、TypeScript 和 RequireJS 来创建一个可工作的模板。本文的应用程序部分是使用 Visual Studio 2015 Community Edition 开发的。
本文中的大多数编码实践都假定您对 Visual Studio 相当熟悉,并且之前已经开发过 ASP.Net MVC 项目、WebAPI 和 Entity Framework。这样做的原因是,我们将更多地关注 AngularJS、RequireJS 和 TypeScript 开发,而不去深入探讨 ASP.Net 和 WebAPI 或 SQL 编码的细节。
开发模板的步骤
本文分为描述工作单元的步骤。每个步骤都包含 Visual Studio 用法说明以及相关的图片,如果需要,还会提供该步骤中相关的代码。有时代码会包含在图片中,您需要查看图片自行编辑代码。您会看到比阅读更多文字更能帮助您理解该怎么做的图片。
本文不涉及面向对象设计、数据库规范化,或者如何有效地将您的客户端代码模块化并为其命名。
本文的目标是提供一个主要关于三个客户端框架/语言/库的可工作原型。它们是 TypeScript、AngularJS 和 RequireJS。所有其他内容都作为实现目标的手段引入。
以下是用于底层应用程序的工具、语言、包及其版本的列表。
- Visual Studio 2015 Community Edition。
- .Net 4.6.1
- Microsoft ASP.Net MVC 5
- Microsoft ASP.Net WebAPI 2.1
- AngularJS 1.4.9
- Twitter bootstrap 3.0.0
- RequireJS 2.1.22
- TypeScript 1.7
- Angularjs.TypeScript.DefinitelyTyped 5.2.1
- Nuget 包管理器
- EntityFramework 6.1.3
- Angular.UI.Bootstrap 1.1.1
- C#
- Microsoft SQLServer
尽管列表很长,但借助 Visual Studio 模板和代码生成器,其中一些项目非常易于使用。
以下是一些您可能感兴趣的主题的链接。
让我们从第一步开始。
步骤 1
本步骤的目标是创建一个单页 Web 应用程序 (SPA)。如果您缺少任何工具或包,您可能需要在本步骤结束前将其添加到您的 Visual Studio 版本中。在本步骤结束时,您可以执行或调试项目,在浏览器中显示一个不含任何内容的网页。
打开 Visual Studio 并创建一个新的 ASP.Net Web 应用程序项目。您可以选择任意项目名称。正如您所见,我使用的是 .Net Framework 4.6.1。版本并不是严格的依赖关系,因为它可能适用于较低版本。不过我没有测试过。
我选择了一个支持 WebApi 的空模板。该项目仅为名义上使用 MVC 框架,我将在开发过程中向您展示 Visual Studio 如何自动添加对 MVC 的支持。
现在我们已经完成了项目创建,下图显示了项目生成的文件夹结构。
接下来,我向项目中添加一个本地 SQLServer 数据库。右键单击解决方案资源管理器中的项目,然后通过菜单导航到添加新项。在左侧的已安装模板中选择 Data,然后选择 SQLServer Database。输入您选择的名称。单击“添加”按钮添加数据库。
该向导会询问数据库文件的位置,接受 App_Data 作为位置,因为它为该文件夹提供了 ASP.Net 默认安全。
上面创建的数据库在第三步之前不会被使用。将其包含在此处的原因是在本步骤结束时准备一个基本的 Web 应用程序。
接下来是添加一个空的 MVC 控制器。请记住,在创建项目时我们没有添加任何对 MVC 的支持。因此,在添加此控制器时,Visual Studio 必须将必要的文件夹、引用和文件添加到项目中。该控制器仅用于打开默认视图,不包含任何读/写操作,也不使用 Entity Framework 进行任何数据库操作。
该控制器名为 LoginController。名称很重要,如果您命名为其他名称,则需要相应地编辑下面的 RouteConfig 文件。
您可以单击“添加”,除非 Visual Studio 设置没有问题,否则您应该会成功添加控制器。可能出现与 MVC 框架支持添加相关的错误。
(在这种错误场景下,您可以绕过 Visual Studio 的过程,手动添加文件夹,编辑视图文件夹中的 web.config 文件,创建并添加视图,然后再添加控制器。如果发生这种情况,您可以添加一些注释,我将提供替代步骤来克服这些问题。另一个步骤是重新从头开始,创建一个全新的项目并重复这些步骤。)
一旦添加了 MVC 控制器,您就可以看到下面的文件夹结构。有一些文件和文件夹对于 AngularJS、TypeScript 和 RequireJS 模板是不需要的,我们可以在接下来的子步骤中删除它们。
“Shared”文件夹、布局和错误 html 文件是不需要的,因为这是一个 SPA(单页应用程序)。
Scripts 文件夹中的一些 JavaScript 文件也是不必要的,以后在处理 Nuget 包管理器时可以删除它们。
当控制器添加后,一个 readme 文件会在源编辑器窗口中打开。遵循该文件中的步骤以使 MVC 和 WebApi 的默认路由正常工作。必须在添加 MVC 路由支持之前注册 WebApi 配置。
以下是 Global.asax.cs 文件的代码内容。
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Http;
namespace ShareIt
{
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
RouteConfig.RegisterRoutes(RouteTable.Routes);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
}
}
}
右键单击“Shared”文件夹,然后选择“Delete”菜单项。
由于我们依赖 RequireJS 来加载我们需要的模块,并且可以使用 TypeScript 按需导入模块,所以我们不需要捆绑任何 JavaScript 或 CSS 文件。我们还在最后一步使用 AngularJS 指令来加载一些 HTML 模板,这些模板是 AngularJS-UI-Bootstrap 包免费提供的。如果您想查看文件下载过程,可以使用开发人员工具(按 F12)并在页面加载时进行查看。
接下来,从“App_Start”文件夹中删除 BundleConfig.cs 文件。
接着删除 ViewStart.cshtml。
RouteConfig.cs 文件被编辑以设置默认控制器。如果您不设置此项,应用程序将无法将流量路由到正确的控制器,并可能导致错误页面。
编辑后的 RouteConfig.cs 文件中的代码如下。
namespace ShareIt
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Login", action = "Index", id = UrlParameter.Optional }
);
}
}
}
如果出现任何代码警告,请使用 intellisense 添加“using”。
右键单击“Login”文件夹,然后添加一个 HTML 页面。将页面命名为 Index.cshtml。由于这是一个 MVC 项目,预期的扩展名是 cshtml,但该文件的内容将不包含任何 Razor 语法。
我们修改 head 的内容如下。body 部分将在本文的第二步中添加。
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<meta http-equiv=X-UA-Compatible content="IE=edge">
<meta name=viewport content="width=device-width, initial-scale=1">
<link rel="Shortcut Icon" type="image/ico" href="~/Content/image/favicon.ico">
<title>Share It</title>
<link href="~/Content/bootstrap.css" rel="stylesheet" />
<link href="~/Content/Site.css" rel="stylesheet" />
</head>
<body>
</body>
</html>
第二步
本步骤开始添加 Angular、RequireJS、Angular 的 DefintelyTyped、TypeScript 支持和 bootstrap 所需的 Nuget 包。
右键单击解决方案资源管理器中的项目,然后打开 Nuget 包管理器窗口。我们需要搜索包,为了方便起见,不要打开控制台,而是使用命令提示符进行安装。如果您事先知道包名称和版本,可以随时采用该方法。我将在本步骤结束时发布 packages.config 的内容,以便高级用户可以使用控制台。
选择“Browse”选项卡,然后键入 requirejs。按所示安装 RequireJS v2.1.22。
接下来,我们安装 RequireJS 的 TypeScript 定义。TypeScript 项目查找定义文件(如类)而不是传统的 JavaScript 函数。类型定义文件提供这些定义。现在下载并安装 requirejs.TypeScript.DefinitelyTyped 包 v0.2.9。
安装完包后,会弹出一个新对话框,询问是否为您的项目添加 TypeScript 支持。您需要对此对话框回答“Yes”,以便包管理器安装所有包含的 JavaScript 库的 TypeScript 类型。
接下来添加 AngularJS 核心包。我们不需要添加所有 AngularJS 模块,只需选择性地添加核心、路由和 sanitize,这对于本项目来说已经足够了。
接下来下载并安装路由模块。
下载并安装 Sanitize,它在后续步骤中作为某些高级 Angular 支持的一部分是必需的。
现在是时候为 Angular 包添加 TypeScript 定义了。
我们在最后一步添加一些 AngularUI 组件,如 Accordian 和 Collapse。添加 AngularUI Bootstrap 包。
接下来两部分非常重要,没有它们,项目将无法构建,我可以看到大量在 Google 上搜索如何解决这些构建错误的帖子。这些错误是由于默认安装的版本引起的,更新是必须的。在 Nuget 包管理器的浏览选项卡中键入 jquery,然后安装 jquery 的 Definitely Typed。Angular 使用这些 jquery 定义。
将安装的 jquery 版本更新到稳定的 2.2.0。
正如在本步骤开始时承诺的那样,packages.config 文件图像如下。一些现有包(如 modernizer)已被移除。
现在是时候向项目中添加一些 TypeScript 代码了。
在解决方案资源管理器中右键单击“Scripts”文件夹,然后添加一个名为“appScripts”的新文件夹。所有用于 Angular 控制器、服务和路由的 TypeScript 代码都将驻留在此处。我本可以为控制器和其他构件添加单独的文件夹,但正如我在本文开头所说,我们只需要一个原型,文件夹结构不是目标。
右键单击“Scripts”文件夹,然后添加一个新 TypeScript 文件,如下所示。将文件命名为 main.ts。
将 typings/angularjs 文件夹内的 "angular.d.ts" 和 "angular-route.d.ts" 文件拖放到源编辑器中打开的 main.ts 文件中,如下所示。
在“appScripts”文件夹中添加一个名为“mainControllers.ts”的新 TypeScript 文件。这是为了定义将在应用程序中使用的控制器集合。在本步骤中,只有一个控制器叫做 MainController,它的职责很琐碎,比如向 $scope 添加几个变量。(代码格式化为 JavaScript,因为没有 TypeScript 格式化可用。)
这个 TypeScript 文件导出名为 mainControllers 的类,任何其他 TypeScript 文件都可以导入这个类并实例化它来创建一个新的类型对象,并调用其中公开的公共方法。在这个类的构造函数内部,创建了一个名为“mainControllers”的新 Angular 模块。这个模块有一个名为“MainController”的控制器,将用于注入 $scope 并填充将在视图中使用的 $scope 变量。
将文件内容添加如下。
"use strict";
export class mainControllers {
constructor() {
var app = angular.module("mainControllers", []);
app.controller('MainController', ($scope, $location) => {
$scope.name = "John";
$scope.lastName = "Doe";
});
}
}
在“appScripts”文件夹中添加一个名为“shareApp.ts”的新 TypeScript 文件。将文件内容复制如下。
"use strict";
import mainCtrlsModule = require("mainControllers");
export class shareApp {
constructor() {
var ngApp = angular.module('shareApp', ["ngRoute", "ngSanitize", "ui.bootstrap", "mainControllers"]);
var mainCtrls = new mainCtrlsModule.mainControllers();
}
}
如前所述,我们使用 RequireJS 来指定模块并称其为“mainCtrlsModule”。我们从中实例化 mainControllers 类。我们正在使用的应用程序模块是“shareApp”,并且我们正在将“ngRoute”、“ngSanitize”、“ui.bootstrap”和“mainControllers”注入到我们创建的 ngApp 模块中。当我们修改 main.ts 以包含更多代码时,这张图会变得更清晰。以下是需要添加到 main.ts 文件中的代码。
requirejs.config({
baseUrl: "Scripts/appScripts",
paths: {
"jquery": "../jquery-2.2.0.min",
"bootstrap": "../bootstrap",
"app": "./shareApp",
"angular": "../angular",
"ngRoute": "../angular-route",
"ngSanitize": "../angular-sanitize",
"mainCtrls": "./mainControllers",
"ui.bootstrap": "../angular-ui/ui-bootstrap-tpls"
},
shim: {
"ngRoute": ['angular']
"ngSanitize": ['angular'],
"ui.bootstrap": ['angular'],
"bootstrap": ['jquery']
}
});
requirejs(["app", "bootstrap", "angular", "ngRoute", "ngSanitize", "ui.bootstrap"], (app) => {
var shareApp = new app.shareApp();
angular.element(document).ready(() => {
angular.bootstrap(document, ['shareApp']);
});
});
现在添加到 main.ts 文件中的内容完成了 RequireJS 模块定义和依赖项管理。
在 config 调用中,指定了应用程序代码所在的基文件夹。下面的 paths 列出了所有代码路径,它们是相对于 baseUrl 指定的文件夹的。shim 用于说明每个模块的依赖关系。例如,bootstrap 依赖于 jquery。在主页面中,当使用响应式布局且菜单折叠时,单击才会展开,前提是 bootstrap JavaScript 代码调用了 jquery 代码。类似地,angular-route 模块依赖于 angular 模块。
我们将主 requirejs 对象通过传递 app 模块作为参数来创建,并创建一个 shareApp 类的实例。我们不指定 html 文件中的 ng-app。这是因为在文档准备好之前,路由将无法正确加载。因此,我们仅在 HTML 文档准备好后才手动引导 angular。
在本步骤中没有路由,我们可能会使用 ng-app angular 指令,但为了准备第三步,这里包含了手动引导。
接下来是修改 index.cshtml 文件。
在 head 部分结束之前,我们添加以下内容
<script data-main="/Scripts/main" src="~/Scripts/require.js"></script>
这就像向任何 html 文件添加正常的 JavaScript 文件引用一样。data-main 是这里重要的部分。它的意思是 RequireJS 应该查找“Scripts”文件夹中一个名为 main.js 的文件,以获取有关配置和初始化的更多信息。即使我们添加了“main.ts”,一个 TypeScript 文件,TypeScript 编译器也会将其转换为 JavaScript 文件。
我们现在将添加一些菜单项和为琐碎的 $scope 变量添加的默认 angular 绑定。生成的 index.cshtml 文件将如下所示。
<!DOCTYPE html> <html> <head> <meta charset=utf-8 /> <meta http-equiv=X-UA-Compatible content="IE=edge"> <meta name=viewport content="width=device-width, initial-scale=1"> <title>Share It</title> <link href="~/Content/bootstrap.css" rel="stylesheet" /> <link href="~/Content/Site.css" rel="stylesheet" /> <script data-main="/Scripts/main" src="~/Scripts/require.js"></script> </head> <body ng-controller="MainController as ctrl"> <div class="navbar navbar-inverse navbar-fixed-top center"> <div class="navbar-inner"> <div class="container center"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="/"> <span class="color-white">Share It</span> </a> </div> <div id="navbar" class="collapse navbar-collapse"> <ul class="nav navbar-nav"> <li><a href="/">Login</a></li> <li><a href="/Register">Register</a></li> <li ng-class="{disablea:!ctrl.loggedIn}"><a href="/Buddies" ng-class="{disablea:!ctrl.loggedIn}">Add Buddies</a></li> <li ng-class="{disablea:!ctrl.loggedIn}"><a href="/Share" ng-class="{disablea:!ctrl.loggedIn}">Share</a></li> </ul> </div> </div> </div> </div> <div id="container" class="container leftrightjustify"> <p>Name : {{name}}</p> <p>LastName : {{lastName}}</p> </div> </body> </html>
在构建项目之前,还有最后一步。右键单击项目并打开属性。
转到 TypeScript Build 属性页,并将 ECMAScript 版本选择为 ECMAScript 5。还将模块系统指定为 AMD。这将启用 TypeScript 类的导出和导入。
在启用模块系统之前进行的任何构建都可能导致构建失败。因此,请在修改 TypeScript Build 属性后立即构建项目。
执行或调试以查看下面的页面。
下面提供了本步骤的源代码。它不包含项目文件或数据库文件。您必须按照步骤进行创建。由于原始编写于大约 3 个月前,一些包版本可能较新。
下载 ShareIt Step2 源代码 - 无项目文件和 DB - 1.5 MB
步骤 3
与前两个步骤相比,本步骤需要添加更多代码。
首先是添加一个 CSS3 样式表。这是本项目唯一的样式表,包含下一个步骤中要添加的 UI 元素所需的样式。
右键单击“Content”文件夹,然后添加一个新样式表。将 css 文件命名为“main”。
将以下内容复制到 main.css 文件并保存。
html, body {
width: 100%;
height: 100%;
color: #004989;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
html {
display: table;
padding-top: 10em;
padding-bottom: 7em;
}
head {
background-image: url(image/favicon.ico);
}
.navbar.center .nav, .navbar.center .nav > li {
float: none;
display: inline-block;
*display: inline;
*zoom: 1;
padding-top: 2%;
}
.center .navbar-inner {
text-align: center;
}
.navbar-brand {
float: left;
height: 19%;
}
.navbar-inverse .navbar-brand:focus, .navbar-inverse .navbar-brand:hover {
outline: none;
cursor: default;
}
.navbar-inverse .navbar-collapse, .navbar-inverse .navbar-form {
float: right;
}
.navbar-brand span {
font: 32px/1.1em Signika,sans-serif;
text-transform: uppercase;
}
.navbar-inverse {
background-color: #004989;
}
.navbar-inverse .navbar-nav > li > a:focus, .navbar-inverse .navbar-nav > li > a:hover {
color: #fff;
outline: none;
}
.navbar-inverse .navbar-nav > li > a:hover {
background-color: #000;
}
.navbar-inverse .navbar-nav > li > a {
color: #8eafe6;
text-transform: uppercase;
font-size: 12px;
}
.in.navbar-collapse {
overflow-x: hidden;
}
.footer-sign {
font-size: xx-small;
float: right;
}
.footer-desc {
color: #fff;
padding-top: 10px;
}
.btn-posn {
margin-top: 2em;
}
.btn-default {
color: #fff;
background-color: #444;
border-color: #444;
cursor: pointer !important;
}
.btn-default.active, .btn-default.focus, .btn-default:active, .btn-default:focus, .btn-default:hover, .open > .dropdown-toggle.btn-default {
color: #fff;
background-color: #ADADAD;
border-color: #ADADAD;
}
.btn.focus, .btn:focus, .btn:hover {
color: #333;
text-decoration: none;
}
input.ng-valid.ng-dirty {
border: #11f61d 2px solid;
box-shadow: 2px 2px 2px 2px;
}
[contenteditable].ng-invalid,input.ng-invalid.ng-dirty {
border: #ee2d12 2px solid;
box-shadow: 2px 2px 2px 2px;
}
[contenteditable] {
border: 1px solid black;
background-color: white;
min-height: 8em;
margin-bottom:20px;
}
.resourceCell {
overflow-y: auto;
max-height: 150px;
border: outset 2px #8eafe6;
box-shadow: 1px 2px 1px 1px #004989;
border-radius: 5px;
}
.linkCollection{
width:100%;
}
.linkCollection > a:link {
background-color: transparent;
display: block;
overflow: hidden;
text-overflow: ellipsis;
border: 2px #8eafe6 solid;
border-radius: 5px;
box-shadow: 2px 2px 2px 2px #004989;
text-decoration: none !important;
padding-left: 3px;
line-height: 2em;
}
.linkCollection>a:visited {
text-decoration: none !important;
background-color: #337ab7;
}
.linkCollection > a:hover {
text-decoration: none !important;
background-color: #ffd800;
cursor:pointer;
}
.linkCollection>a:active {
text-decoration: none !important;
background-color: #ffd800;
}
.tooltip-inner{
background-color:#ffd800 !important;
color:#004989;
}
.disablea{
cursor:none;
opacity:0.5;
}
在 main.cshtml 文件中添加对 css 文件的引用,如下所示。
此时可以为应用程序添加一个可选的图标文件。在“Content”文件夹中创建一个“image”文件夹,然后创建一个“favicon.ico”文件。这是可选的,如何创建它可以在网上找到。
现在双击“App_Data”文件夹中的 mdf 文件,打开服务器资源管理器。
右键单击 Tables,然后单击“New Query”菜单项以打开 SQL 查询编辑器窗口。
在查询编辑器中,粘贴以下 SQL 代码。
CREATE TABLE [dbo].[Student] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[FirstName] NVARCHAR (50) NOT NULL,
[LastName] NVARCHAR (50) NOT NULL,
[UserName] NVARCHAR (8) NOT NULL,
[Password] NVARCHAR (10) NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC),
UNIQUE NONCLUSTERED ([UserName] ASC)
);
GO
CREATE TABLE [dbo].[Buddy] (
[Id] INT IDENTITY (50, 1) NOT NULL,
[StudentId] INT NOT NULL,
[BuddyId] INT NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC),
UNIQUE NONCLUSTERED ([StudentId] ASC, [BuddyId] ASC)
);
GO
CREATE TABLE [dbo].[Resource] (
[Id] INT IDENTITY (100, 1) NOT NULL,
[StudentId] INT NOT NULL,
[resource] VARBINARY (MAX) NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
);
GO
在数据库开发中,为了支持 DB 原则,我应该使 Buddy 表中的 StudentId 成为 Student 表的外键。Resource 表也是如此。但正如我提到的,这里没有遵循数据库原则,只是为了数据存储和检索而想存储和检索数据。
在 Buddy 表中,StudentId 和 BuddyId 是唯一的,不允许重复。Password 仅为名义上的,不用于登录。但是登录时需要输入一些字符串。
执行查询,以便创建三个表。
现在是时候创建遵循数据库中创建的表的模型了。右键单击“Models”文件夹,然后单击添加新项。
我们添加 ADO.Net Entity Data Model,并且模板生成器免费为我们提供了 DataContext 以及读写应用程序数据的支持代码。为模型命名。我使用 ShareItModel。
在向导的下一步是选择模型的内容。我选择 Entity Framework Designer。edmx 文件将被生成,并且所有生成的实体的图可以被查看。
接受下一步向导中的默认设置,因为它包含了要添加到 web.config 文件中的连接属性。
在下一步中,选择了所有三个已创建的表来生成它们的类以及填充和检索数据的支持代码。
我们可以结束向导,支持类就会被创建。edmx 图可以关闭,因为它不有用,而且我们不会在本项目中更改它。
此时需要构建项目。如果现在不构建项目,添加使用这些模型类的 WebApi 控制器将不会成功。
执行或调试项目以查看在 CSS3 应用之前出现的情况。
下一项是添加 WebApi 控制器。右键单击“Controllers”文件夹,然后单击“Controller..”。这是为了添加一个新的控制器。选择带有使用 entity Framework 的操作的 WebAPI 2 控制器。
接下来是命名控制器并选择模型类和 DataContext 类。
单击“Add”,生成的代码是 Controllers 文件夹中的一个名为 StudentsController.cs 的文件。需要修改该文件以支持登录 api 调用并屏蔽密码数据。密码在此应用程序中不用于任何身份验证或授权,仅用于演示。添加一个名为“utils”的静态类并添加一个方法。(长时间使用 JavaScript 导致我犯了错误,比如类名开头没有大写字母)。
index.cshtml 文件内容被修改,删除了琐碎的 $scope 变量,并添加了对学生登录的支持。
Students 控制器中附加的 api 调用的代码如下。在返回学生之前,通过调用 utils.CensorStudent 来屏蔽它。
// GET api/Students?userName="test"
[ResponseType(typeof(Student))]
public IHttpActionResult GetStudent(string userName)
{
Student student = db.Students.Where(s => s.UserName == userName).First();
if (student == null)
{
return NotFound();
}
utils.CensorStudent(student);
return Ok(student);
}
支持登录表单的 html 代码粘贴在下面。
<div id="container" class="container leftrightjustify">
<h3 ng-show="ctrl.loggedIn">{{ctrl.loggedInUser.FirstName + " " + ctrl.loggedInUser.LastName}} is logged in.</h3>
<h3 ng-show="ctrl.message.length">{{ctrl.message}}</h3>
<hr />
<div ng-view></div>
<div ng-hide="ctrl.loggedIn || ctrl.location.path() !== '/'" class="col-lg-4 col-lg-offset-4 form-main">
<h2>Please Login</h2>
<form name="loginForm" ng-submit="loginForm.$valid && ctrl.login()" novalidate>
<div class="form-group">
<label>User Name :</label>
<input type="text" ng-model="ctrl.user.userName" placeholder="Enter user name" class="ng-pristine ng-valid form-control" required>
</div>
<div class="form-group">
<label>Password:</label>
<input type="password" ng-model="ctrl.user.password" placeholder="***********" class="ng-pristine ng-valid form-control" required>
</div>
<input type="submit" class="btn btn-primary btn-default" value="Login" required />
</form>
</div>
</div>
由于 WebAPI 控制器代码已准备就绪,现在需要编写 angular http 服务调用。需要在“appScripts”文件夹中添加一个新的 TypeScript 文件。将文件命名为 serviceHandler.ts。
serviceHandler 中的代码如下。
"use strict";
import ng = angular;
interface IServiceHandler {
assign(service: exportService): void;
validateUser<T>(userName: string): ng.IHttpPromise<T>;
registerUser<T>(user: T): ng.IHttpPromise<T>;
}
export class exportService {
$http: any;
constructor($http: ng.IHttpService) {
this.$http = $http;
return this;
}
}
export class serviceHandler implements IServiceHandler {
service: exportService;
constructor() {
}
public assign(service: exportService): void {
this.service = service;
}
public validateUser<T>(userName: string): ng.IHttpPromise<T> {
return this.service.$http({
method: 'GET',
url: '/api/Students?userName=' + userName
});
};
public registerUser<T>(user: T): ng.IHttpPromise<T> {
return this.service.$http({
method: 'POST',
url: '/api/Students',
data: user
});
};
}
声明了一个接口,其中包含任何实现该接口的类需要实现的方法。exportService 是一个类,它保持注入的 $http,然后将 exportService 分配给 serviceHandler,以便后续调用可以使用它。
在第二步中,mainControllers 使用一个没有名字且包含几个 $scope 变量的琐碎控制器。现在是时候在 appScripts 文件夹中添加一个 loginController.ts 文件了。
在 loginController 中,serviceHandler 用于执行 Get 和 Post 等 http 调用。 $http 不在此 loginController 中注入,但在此代码执行时,Angular 已经将 $http 注入到 serviceHandler 中。代码如下。
"use strict";
import ng = angular;
import serviceModule = require("serviceHandler");
export class loginController {
location: ng.ILocationService;
loggedIn: boolean;
message: string;
user: any;
serviceFactory: serviceModule.serviceHandler;
loggedInUser: any;
constructor($location: ng.ILocationService, services: any, serviceClass: serviceModule.serviceHandler) {
this.serviceFactory = serviceClass;
this.serviceFactory.assign(services);
this.location = $location;
this.loggedIn = false;
this.message = "";
this.user = {};
}
public login(): void {
var self = this;
this.serviceFactory.validateUser(this.user.userName).then(function (response) {
if (response.status === 200) {
self.loggedIn = true;
self.message = "";
self.loggedInUser = response.data;
}
}).catch(function (response) {
self.loggedIn = false;
self.message = response.data.Message + ";" + response.data.ExceptionMessage;
});
self.user = {};
};
public validate(): void {
if (!this.loggedIn) {
this.message = "Login before adding buddies or share."
this.location.path("/");
}
};
}
接下来是添加一个路由器。这是一个客户端路由器,代码和 html 通过 XmlHttpRequest 下载。正如我之前所说,使用开发人员工具并在页面加载和路由发生时查看它的运行情况。右键单击“appScripts”并添加一个名为 configRouter.ts 的 TypeScript 文件。
configRouter 的内容如下。为了使其正常工作,还需要添加一些文件和控制器代码,这些将在本步骤的剩余部分和下一步中完成。
"use strict";
import ng = angular;
import ngr = angular.route;
export class configRouter {
constructor() {
};
public configure($routeProvider: ngr.IRouteProvider, $locationProvider: ng.ILocationProvider): void {
$routeProvider.when("/Register",
{
templateUrl: "PartialViews/Register.html",
controller: "RegisterController"
}).when("/Buddies", {
templateUrl: "PartialViews/Buddies.html",
controller: "BuddiesController"
}).when("/Share", {
templateUrl: "PartialViews/Share.html",
controller: "ShareController"
}).otherwise({
redirectTo: "/"
});
$locationProvider.html5Mode({
enabled: true,
requireBase: false
});
}
}
新添加的文件需要反映在 RequireJS main 文件中的 paths 中。请看下图。
现在需要创建部分视图。在项目的根目录下创建一个名为“PartialViews”的文件夹。
在此文件夹中添加一个名为“Register.html”的新 html 文件。
此 html 文件的内容如下。此内容将作为 Angular ng-view 的内容提供。换句话说,这段 html 代码将被插入到指定 ng-view 的位置。这就是为什么这个文件不遵循带有 html、body 标签等的标准 HTML5 文档结构的原因。
<div class="col-sm-4 col-sm-offset-4 form-main">
<h2>Register a new user</h2>
<form name="registerForm" ng-controller="RegisterController as regCtrl" ng-submit="registerForm.$valid && regCtrl.register()" novalidate>
<div class="form-group">
<label>First Name :</label>
<input type="text" ng-model="regCtrl.user.firstName" class="ng-pristine ng-valid form-control" placeholder="Enter first name" required />
</div>
<div class="form-group">
<label>Last Name :</label>
<input type="text" ng-model="regCtrl.user.lastName" class="ng-pristine ng-valid form-control" placeholder="Enter last name" required />
</div>
<div class="form-group">
<label>User Name :</label>
<input type="text" ng-model="regCtrl.user.userName" class="ng-pristine ng-valid form-control" placeholder="Enter user name" required />
</div>
<div class="form-group">
<label>Password:</label>
<input type="password" ng-model="regCtrl.user.password" class="ng-pristine ng-valid form-control" placeholder="***********" required />
</div>
<input type="submit" class="btn btn-primary btn-default" value="Add User" />
</form>
</div>
接下来添加控制器“RegisterController”。右键单击“appScripts”文件夹以添加一个新的 TypeScript 文件。将其命名为“registerController”。
RegisterController 的代码如下。
"use strict";
import ng = angular;
import serviceModule = require("serviceHandler");
import loginCtrlModule = require("loginController");
export class registerController {
location: ng.ILocationService;
user: any;
serviceFactory: serviceModule.serviceHandler;
parent: any;
constructor($scope: ng.IScope, $location: ng.ILocationService, services: serviceModule.serviceHandler) {
this.serviceFactory = services;
this.location = $location;
this.parent = $scope.$parent;
this.user = {};
}
public register(): void {
var self = this;
this.serviceFactory.registerUser(this.user).then(function (response) {
if (response.status === 201) {
self.parent.ctrl.message = "";
self.location.path("/");
}
}).catch(function (response) {
self.parent.ctrl.message = response.data.Message + ";" + response.data.ExceptionMessage;
});
self.user = {};
};
}
现在需要将所有这些控制器、服务工厂和路由器进行关联。这将包含在 mainControllers 文件中。之前执行两个 $scope 变量添加的匿名控制器已被移除。
创建 configRouter 类实例,并将公共方法 configure 注入 $routeProvider。根据路由,设置控制器,并在客户端应用 html 模板,然后渲染视图。
使用 factory 创建了一个服务,并将注入的 $http 设置在 serviceHandler 对象中。目前有两个控制器:loginController 和 registerController。register 控制器的父作用域是 loginController,可以通过它来查询登录状态。
"use strict";
import ng = angular;
import serviceModule = require("serviceHandler");
import loginCtrlModule = require("loginController");
import routerModule = require("configRouter");
import regCtrlModule = require("registerController");
export class mainControllers {
constructor() {
var app = angular.module("mainControllers", []);
var router = new routerModule.configRouter();
app.config(router.configure);
var serviceHandler = new serviceModule.serviceHandler();
var serviceMod = app.factory("services", ["$http", serviceModule.exportService]);
app.controller('MainController', ($location, services) => new loginCtrlModule.loginController($location, services, serviceHandler));
app.controller('RegisterController', ($scope, $location, services) => new regCtrlModule.registerController($scope, $location, serviceHandler));
}
}
最后是 main.ts 文件代码。此文件中的主要变化是代码路径。
paths: {
"jquery": "../jquery-2.2.0.min",
"bootstrap": "../bootstrap",
"app": "./shareApp",
"angular": "../angular",
"ngRoute": "../angular-route",
"ngSanitize": "../angular-sanitize",
"mainCtrls": "./mainControllers",
"loginCtrl": "./loginController",
"routerCfg": "./configRouter",
"serviceFactory": "./serviceHandler",
"ui.bootstrap": "../angular-ui/ui-bootstrap-tpls"
},
以下图片用于注册新学生和学生登录。目前密码不使用。
关注点
项目多次未能构建或未能正确显示页面。以下是一些列出的情况。
- 如果项目需要版本控制,则包文件夹的位置非常重要。我将包文件夹移到了与项目文件相同的级别,以便 Nuget 包管理器能够正确更新引用。
- TypeScript 模块系统规范对于正确导出模块很重要。
- 指定 ECMAScript 6 而不是 5 可能会导致与“Duplicate identifier 'Promise'”相关的错误。
- requirejs 调用中的可注入模块列表很重要,否则可能会导致依赖项未正确加载。
- Angular 路由和 requirejs 模块下载是相互关联的,这可能会导致运行时 Angular JavaScript 错误。使用手动引导。