Angular2 在 ASP.NET MVC & Web API 中 - 第 1 部分






4.93/5 (159投票s)
本文将帮助初学者在 ASP.NET MVC 中集成 Angular2,在 ASP.NET MVC Web API 中创建 RESTful API,并在 Angular2 中构建前端。
系列文章
- 第 1 部分:在 Visual Studio 2017 中设置 Angular2,基本的 CRUD 应用程序,第三方模态弹出窗口控件
- 第 2 部分:使用 Angular2 管道进行过滤/搜索,全局错误处理,客户端调试
- 第 3 部分:Angular 2 到 Angular 4,包含 Angular Material 组件
- 第 4 部分:Angular 4 数据网格,支持导出到 Excel、排序和过滤
引言
这是一篇初学者级别的文章,写给有基本编程知识,希望从零开始学习 Angular2、C# 和 RESTful API 的新手开发者和学生。如果您是经验丰富的开发者,只需要概览,可以下载附件项目并自行查看。您需要 Visual Studio 和 SQL Server Management Studio 来配合本文开发项目并编译附件项目。
让我们开始...
设置 Angular2 环境
- 打开 Visual Studio。我使用的是 Visual Studio 2017 Community,您也可以使用安装了Node.js和
TypeScript
包的 Visual Studio 2015 Community。(阅读更多关于 Visual Studio 2015 Update 3 的信息。) - 转到“文件”菜单,选择文件 -> 新建 -> 项目
- 输入项目名称并选择您想要的 .NET Framework(本文我使用的是.NET Framework 4.6)。点击确定按钮
- 在下一个屏幕上选择MVC,并勾选添加文件夹和核心引用选项中的Web API,因为我们将创建 RESTful API 进行 CRUD 操作。点击确定按钮
- 基本的 ASP.NET MVC 项目已创建。下一步是为 Angular2 应用程序准备它。我们将在接下来的步骤中进行。
- 右键单击项目
Angular2MVC
,选择添加 -> 新建项 - 在右上角的搜索框中输入package.json,
npm 配置文件
将被过滤出来。点击添加按钮将package.json添加到项目中 - 我们将使用NPM(Node 包管理器)配置文件来管理所有 Angular2 包。要阅读更多关于 NPM 的信息,请访问此链接。
- 接下来,从 Angular2 Quick Start GitHub 链接复制 Package.json 并粘贴到
Angular2MVC
项目中新添加的package.json文件中 - 在package.json文件的 dependencies 部分,我们可以看到所有 Angular2 相关的包。要了解
^
和~
符号的区别,请在此处查看。 - 右键单击package.json文件,选择
还原包
选项。Visual Studio 的Node.js工具将下载package.json文件中提到的所有依赖包。将来如果您需要任何额外的包,只需将其添加到DevDependencies
部分并还原即可,这真的能让工作更轻松。 - 您会在项目中找到一个新文件夹node_modules,其中包含所有下载的包
- 下一步是让我们的项目知道如何获取这些包。我们将添加systemjs.config.js文件。右键单击
Angular2MVC
项目,选择添加 -> JavaScript 文件 - 在项名称字段中输入systemjs.config.js,然后单击添加按钮
- 从 Angular2 Quick Start GitHub 复制systemjs.config.js文件的内容,并粘贴到
Angular2MVC
项目中新添加的systemjs.config.js文件中 - 接下来,我们通过右键单击
Angular2MVC
项目并选择添加 -> 新建项来添加TypeScript JSON 配置文件。选择TypeScript JSON 配置文件,然后单击添加按钮 - 从 Angular2 Quick Start GitHub 复制tsconfig.js文件的内容,并替换
Angular2MVC
项目中新添加的tsconfig.js文件 - 如果您在尝试构建时遇到编译错误,请不用担心。只要我们开始添加任何TypeScript 文件,这些错误就会消失。
- 现在我们在 ASP.NET MVC 中的 Angular2 设置已基本完成,是时候开发用户管理应用程序了,但首先我们需要一个数据库,其中包含一个表来存储用户信息。
创建用户数据库 & 实体框架模型
CREATE TABLE [dbo].[TblUser] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[FirstName] NVARCHAR (250) NULL,
[LastName] NVARCHAR (250) NULL,
[Gender] NVARCHAR (250) NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
);
- 右键单击App_Data文件夹,选择添加 -> 新建项。在数据部分,您可以找到SQL Server 数据库选项。选择它并将其命名为
UserDB
。 - 数据库创建后,双击UserDB.mdf数据库文件以打开
表
- 右键单击UserDB.mdf,选择新建查询。粘贴以下 SQL 查询以创建
TblUser
表,然后单击执行按钮创建表 - 右键单击表文件夹,选择刷新选项
- 接下来,我们将开发ASP.NET MVC端,包括设置布局和索引页面以加载 Angular2 主页,以及用于加载索引视图的 MVC 控制器,以及用于 RESTful CRUD(
创建
、读取
、更新
和删除
)用户 API 的 Web API 2.0 控制器。 - 首先,我们转到App_Start文件夹并配置路由以接受任何 URL,因为我们可以在 Angular2 中定义自己的自定义路由(将在后续步骤中完成)。双击RouteConfig.cs文件进行编辑,并将默认路由中的 URL 更改如下
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; namespace Angular2MVC { public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}") routes.MapRoute( name: "Default", url: "{*anything}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } } }
- 接下来,让我们打开_Layout.cshtml,对其进行一些清理,并添加运行 Angular2 应用程序所需的重要JavaScript文件。打开Views -> Shared -> _Layout.cshtml文件。删除预先添加的顶部菜单和页面链接。在 header 部分添加以下 JS 文件和
system.import
语句<script src="/node_modules/core-js/client/shim.min.js"></script> <script src="/node_modules/zone.js/dist/zone.js"></script> <script src="/node_modules/systemjs/dist/system.src.js"></script> <script src="/systemjs.config.js"></script> <script> System.import('app').catch(function(err){ console.error(err); }); </script>
对这些 JS 文件做一个简要介绍。
Zone.js: Zone 是一个跨异步任务的执行上下文。有关更多信息,请单击此处。
System.src.js & system.import(‘app’)
: 可配置的模块加载器,可在浏览器和 NodeJS 中实现动态 ES 模块工作流。有关更多信息,请单击此处。 - 最终的_Layout.cshtml应该如下所示
- 接下来,让我们为
UserDB
数据库创建ADO.NET Entity Data Model
。右键单击Angular2MVC
项目,选择添加 -> 新建文件夹
,将名称指定为DBContext
或您想要的任何名称 - 右键单击新创建的DBContext文件夹,选择添加 -> 新建项
- 在左侧面板的Visual C#下,选择数据。在右侧,选择ADO.NET Entity Data Model。输入名称
UserDBEntities
或您选择的任何名称。单击添加按钮。 - 在下一个屏幕上,选择EF Designer for Data,然后单击Next按钮
- 在下一个屏幕上单击新建连接按钮
- 在下一个屏幕上,如果数据源未选择为Microsoft SQL Server Database file (SqlClient),请单击更改按钮并选择它:
- 在数据库文件名中,单击浏览按钮:
- 浏览到前面步骤中创建并保存在App_Data文件夹中的
UserDB
数据库,然后单击选择 SQL Server 数据库文件和连接属性窗口上的确定按钮 - 勾选将连接设置保存在 Web.Config 中复选框,然后单击Next按钮
- 在下一个屏幕上,您可以选择实体框架版本。我使用的是6.x,您可以根据自己的选择使用
- 在下一个屏幕上,单击表复选框。您将只看到一个表
TblUser
。单击Finish按钮以结束向导 - 这需要几秒钟,最后,您将看到我们的数据库实体模型,其中包含一个表
- 此时,如果您尝试编译项目,可能会遇到很多
TypeScript
错误。为了解决这个问题,请创建一个名为app
的文件夹,右键单击它,选择添加 -> TypeScript 文件 - 输入名称main.ts并单击OK(我们将在接下来的步骤中使用此文件)。现在重新生成项目,它应该会成功生成。
开发用户管理 RESTful API
- 下一步是创建用于
读取
、添加
、更新
和删除
用户的ASP.NET MVC Web API。 - 首先,我们将创建父 API 控制器,它将包含所有 API 控制器共享的通用方法。目前,我们将只有一个方法可以将类对象序列化为 JSON 字符串,以供 Angular2 前端使用,并用于
UserDB
数据库DBContext
对象,以便在子控制器中执行数据库操作。右键单击Controllers文件夹,选择添加 -> Controller… - 选择Web API 2 Controller – Empty,然后单击添加按钮
- 输入名称
BaseAPIController
,然后单击添加按钮 - 在
BaseAPIController
中添加以下代码protected readonly UserDBEntities UserDB = new UserDBEntities(); protected HttpResponseMessage ToJson(dynamic obj) { var response = Request.CreateResponse(HttpStatusCode.OK); response.Content = new StringContent(JsonConvert.SerializeObject(obj), Encoding.UTF8, "application/json"); return response; }
-
上面提供的代码解释得很清楚,我们正在创建一个名为
UserDB
的UserDBEntities
类对象,通过它可以调用加载
、添加
、更新
和删除
用户的方法。ToJson
方法接收任何类型的类对象,创建一个带有OK HttpStatusCode
的HTTP 响应对象,并通过调用Newtonsoft.json库中的JsonConvert
方法将对象序列化为 JSON 字符串。最终代码应如下所示 - 接下来,让我们为用户管理创建RESTful Web API,即从数据库加载所有用户、添加新用户、更新和删除现有用户。我们将创建以下方法
- 用于读取所有用户的
GET
方法 - 用于创建新用户的
POST
方法 - 用于更新现有用户的
PUT
方法 - 用于删除现有用户的
DELETE
方法
- 用于读取所有用户的
- 要阅读更多关于HTTP Verbs和方法的信息,请单击此处。
- 右键单击Controllers文件夹,选择添加 -> Controller…
- 选择Web API 2 Controller – Empty,然后单击添加按钮
- 输入名称
UserAPIController
,然后单击添加按钮 - 用以下代码替换
UserAPIController
类代码public class UserAPIController : BaseAPIController { public HttpResponseMessage Get() { return ToJson(UserDB.TblUsers.AsEnumerable()); } public HttpResponseMessage Post([FromBody]TblUser value) { UserDB.TblUsers.Add(value); return ToJson(UserDB.SaveChanges()); } public HttpResponseMessage Put(int id, [FromBody]TblUser value) { UserDB.Entry(value).State = EntityState.Modified; return ToJson(UserDB.SaveChanges()); } public HttpResponseMessage Delete(int id) { UserDB.TblUsers.Remove(UserDB.TblUsers.FirstOrDefault(x => x.Id == id)); return ToJson(UserDB.SaveChanges()); } }
UserAPIController
继承自BaseAPIController
,以使用UserDB
对象和ToJson
方法将User
实体转换为 JSON 字符串并将其保存在 HTTP 响应消息中。Get()
: 从数据库加载所有用户,并返回包含转换为JSON
字符串的用户实体的 HTTP 响应消息。Post([FromBody]TblUser value)
: 从前端接收用户信息并将其保存到数据库。成功保存则返回1
。Put(int id, [FromBody]TblUser value)
: 接收现有用户 ID 和更新的信息,并将其更新到数据库。成功更新则返回1
。Delete(int id)
: 接收现有用户 ID,按 ID 加载用户并删除它。成功删除则返回1
。
- 最终的
UserAPIController
类应如下所示
开发 Angular2 应用程序
- 现在让我们开始激动人心的部分,编写
Angular2
代码。在实际编写代码之前,理解Angular2
架构非常重要。由于我不会专注于编写 Angular2 的内容,因为您可以找到大量的教程和免费视频,让我们回顾一下Angular2
的基本结构,如果您懒得去 angular.io 网站,可以从这里获取。Modules
: 每个 Angular 应用至少有一个 Angular 模块类,即根
模块。应用程序通过引导其根模块来启动。在开发过程中,您很可能会在main.ts文件中引导AppModule
,我们将在后续步骤中创建该文件。根模块通常命名为AppModule
。在AppModule
中,我们指定应用程序使用的所有组件、服务或自定义管道。Components
: 组件控制屏幕上的视图,您可以定义属性和方法来控制视图。如果您曾经使用过 ASP.NET 窗体,我会说组件就像代码隐藏文件aspx.cs文件,您可以通过方法和属性与 aspx 文件进行交互。Templates
: 您通过伴随的模板定义组件的视图。模板是 HTML 的一种形式,它告诉 Angular 如何渲染组件。根据我前面步骤的例子,它就像 ASP.NET 窗体中的aspx文件。Metadata
: 元数据告诉 Angular 如何处理一个类。如果您看到一个组件,它只是一个类,元数据告诉它与该组件关联的模板(代码隐藏或 HTML)、任何样式表或如何通过Selector
属性使用该组件。Data binding
: 简单来说,信息或控件如何在模板和组件之间传输。例如,当您单击模板中的任何按钮时,如何获取组件中的click
事件并执行您的逻辑。Angular2 提供以下类型的数据绑定{{}} interpolation
显示组件中声明的任何变量值。[ ] property binding
用于将值从父组件发送到子组件。我们将在未来的章节中使用它。( ) event binding
用于将任何事件从模板捕获到组件,例如 (click
)。
Directives
: 有两种指令:结构性
指令和属性
指令。- 结构性指令通过在 DOM 中添加、删除和替换元素来改变布局,例如,
*ngFor
和*ngIf
用于遍历 HTML 元素并显示/隐藏元素。 - 属性指令改变现有元素的外观或行为。在模板中,它们看起来像常规的 HTML 属性,因此得名,例如,
ngStyle
用于样式表,ngModel
用于双向数据绑定。
- 结构性指令通过在 DOM 中添加、删除和替换元素来改变布局,例如,
- Services: 服务是一个广泛的类别,涵盖了您的应用程序所需的任何值、函数或功能。关于服务,Angular2 没有特定之处。Angular2 没有服务的定义。没有服务基类,也没有注册服务的地方。服务的例子包括
Error
、Log
、HTTP
等。Component
应仅充当模板和用户之间的协调者角色。它应该将其余功能,例如从服务器获取数据、删除或更新、日志记录、显示错误等委托给服务。 - Dependency injection: 依赖注入是一种为类提供完全满足其所需依赖关系的新实例的方法。大多数依赖项都是服务。Angular2 使用依赖注入为新组件提供它们所需的服务。例如,对于
HTTP
服务,我们将在后续步骤中使用依赖注入将服务实例提供给组件。 - 有关更多详细信息和更好的理解,请单击此处。
- 希望您已经对 Angular2 架构有了基本了解。让我们使用 ASP.NET MVC 中的 Angular 2 和 RESTFul API 作为后端服务,创建一个用户管理页面(
添加
、更新
、删除
和查看
用户)。 - 在我们的项目中,我们将按照约定在app文件夹中创建所有 Angular2 相关代码。如果您还没有创建app文件夹,请继续创建。如果您遵循了前面的步骤,应该会在app文件夹中有一个名为main.ts的 TypeScript 文件,我们将使用它来引导
AppModule
。 - 在继续之前,让我展示一下最终的应用程序将是什么样子。它将有两个页面:一个是只有大图片的首页,另一个是带有用户信息的表格视图,每个记录旁边都有编辑和删除按钮,以及表格顶部的添加按钮以添加新用户。每个按钮都会打开一个模态弹出窗口,您可以在其中执行相应的功能。以下是两个页面和每个功能的屏幕截图
- 现在您对最终应用程序有了基本了解。让我们开始开发应用程序的 Angular2 部分。让我们牢记 Angular2 架构,并创建应用程序的基本架构。
- 首先,让我们创建 Angular2
模块
,它将是应用程序的入口点。右键单击app文件夹,选择添加 -> TypeScript 文件 - 如果您在第二个菜单中看不到TypeScript 文件,请右键单击app文件夹,选择添加 -> 新建项,搜索TypeScript,然后选择TypeScript 文件,输入名称并选择OK按钮
- 将新 TypeScript 文件命名为app.module.ts,然后单击OK按钮
- 在新添加的app.module.ts中添加以下代码
import { NgModule } from '@angular/core'; import { APP_BASE_HREF } from '@angular/common'; import { BrowserModule } from '@angular/platform-browser'; import { ReactiveFormsModule } from '@angular/forms'; import { HttpModule } from '@angular/http'; @NgModule({ imports: [BrowserModule, ReactiveFormsModule, HttpModule], declarations: [], providers: [{ provide: APP_BASE_HREF, useValue: '/' }], bootstrap: [] }) export class AppModule { }
- 只是为了刷新您的记忆,我们正在使用TypeScript和 Angular2。如果您想了解更多关于 TypeScript 的信息,请单击此处。
- 如果您快速浏览
AppModule
类,您可以看到我们正在导入所需的库,例如 Angular Core 中的NgModule
。同样,我们将使用Reactive forms
来处理用户,我们从 Angular Forms 包中导入ReactiveFormModule
。我们将继续扩展app.module.ts文件,添加用户组件、服务、模态弹出窗口等。- 在
NgModule
元数据部分Imports
包含模块列表。Declarations
包含组件列表,我们将在下一步添加用户组件。Providers
包含服务列表。我们将添加一个具有HTTP
操作的服务来执行用户读取、添加、更新和删除操作。目前,它具有基本的href
路径。Bootstrap
包含入口组件,我们将在下一步创建app.component.ts文件并在此处添加它。
- 在
- 下一步是编辑app文件夹中的main.ts文件,并在其中添加以下代码
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app.module'; platformBrowserDynamic().bootstrapModule(AppModule);
- main.ts代码解释得很清楚。
AppModule
引用是从当前文件夹导入的,使其成为入口模块,并使用platformBrowserDynamic
模块的bootstrapModule
函数加载其他辅助资源(引导)。Bootstrap
函数初始化 Angular2 应用程序,加载所需的组件、服务或其他辅助资源以运行应用程序。尝试构建项目以避免在后续步骤中出现任何错误。 - 接下来,创建两个 TypeScript 文件,app.component.ts和app.routing.ts,用于主应用程序组件和路由表。我们稍后会回来处理它们。右键单击app文件夹,选择添加 -> TypeScript 文件
- 输入名称app.component.ts,然后单击OK按钮
- 再次单击app文件夹,选择添加 -> TypeScript 文件,输入名称app.routing.ts,然后单击OK按钮
- 接下来,让我们创建只有一张大图片的Home组件
- 我们将在新文件夹中创建所有用户组件。右键单击app文件夹,选择添加 -> 新建文件夹,输入名称Components
- 右键单击新创建的Component文件夹,选择添加 -> TypeScript 文件
- 输入名称home.component.ts,然后单击OK按钮
- 在新创建的home.component.ts文件中添加以下代码
import { Component } from "@angular/core"; @Component({ template: `<img src="../../images/users.png" style="text-align:center"/>` }) export class HomeComponent{ }
- 在
HomeComponent
中,您可以在元数据的 template 属性中看到,我们有一个来自根 images 文件夹的纯 HTML 图像元素,它将在屏幕上显示users.png。您可以获取任何图片,将其保存在images文件夹中,然后在HomeComponent
中加载。 - 右键单击
Angular2MVC
项目,选择添加 -> 新建文件夹,输入文件夹名称为images - 右键单击新添加的images文件夹,选择在文件资源管理器中打开,将下面提供的图像复制到打开的位置
- 我们的
HomeComponent
已完成,让我们在屏幕上查看它。我们需要再完成几个步骤。我们将做的第一件事是创建路由
表。如果您使用过 ASP.NET MVC,这个路由表就相当于 MVC 路由表。我们将为不同的视图组件定义自定义路由。第二步,我们将创建主应用程序组件,其中将创建导航菜单并加载所有视图组件。- 双击app文件夹中的app.routing.ts进行编辑,并添加以下代码
import { ModuleWithProviders } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { HomeComponent } from './components/home.component'; const appRoutes: Routes = [ { path: '', redirectTo: 'home', pathMatch: 'full' }, { path: 'home', component: HomeComponent } ]; export const routing: ModuleWithProviders = RouterModule.forRoot(appRoutes);
- 在上面的代码中,我们从 angular router 包导入了路由库,并从components文件夹导入了最近创建的
HomeComponent
。在 app routers 中,path
属性是浏览器地址栏中可见的实际 URL,例如 https://:4500/home。一旦我们创建了UserComponent
,我们将为其添加另一个路由。 - 接下来双击app文件夹中的app.component.ts进行编辑,并添加以下代码
import { Component } from "@angular/core" @Component({ selector: "user-app", template: ` <div> <nav class='navbar navbar-inverse'> <div class='container-fluid'> <ul class='nav navbar-nav'> <li><a [routerLink]="['home']">Home</a></li> </ul> </div> </nav> <div class='container'> <router-outlet></router-outlet> </div> </div> ` }) export class AppComponent { }
AppComponent
很简单,它有一个包含已知引导代码的模板,用于创建只有一个 home 链接的导航栏。Home 页面的 routerlink home 名称是我们之前在app.routing.ts路由表中定义的。您可以根据需要定义任何名称,例如 default、index 等。router-outlet
充当动态加载的视图组件的占位符。我们还在AppComponent
元数据部分定义了selector
属性(user-app
),因为我们将在 MVC 视图(index.cshtml)中使用此selector
来引导AppComponent
并加载它。有关router-outlet
的更多信息,请单击此处。- 所以我们已经创建了应用程序组件(
AppComponent
),让我们转到AppModule
并 along路由
表注册HomeComponent
和AppComponent
。之后,我们将添加AppComponent
进行引导。要完成所有这些,请按照以下方式更新您的app.module.tsimport { NgModule } from '@angular/core'; import { APP_BASE_HREF } from '@angular/common'; import { BrowserModule } from '@angular/platform-browser'; import { ReactiveFormsModule } from '@angular/forms'; import { HttpModule } from '@angular/http'; import { AppComponent } from './app.component'; import { routing } from './app.routing'; import { HomeComponent } from './components/home.component'; @NgModule({ imports: [BrowserModule, ReactiveFormsModule, HttpModule, routing], declarations: [AppComponent, HomeComponent], providers: [{ provide: APP_BASE_HREF, useValue: '/' }], bootstrap: [AppComponent] }) export class AppModule { }
- 您可以看到我们导入了
HomeComponent
和AppComponent
,将它们添加到 declaration 中,并将AppComponent
作为应用程序的入口点进行引导。(引导不仅仅是一个入口点,正如我们在前面的步骤中讨论过的,您可以搜索 Google 以完全理解它。这里为了简单起见,我只将其称为入口点)。 - 我们几乎快要运行我们的 Angular2 应用程序并查看主页了。转到Views -> Home,然后双击Index.cshtml进行编辑
- 删除现有代码,并输入以下代码行
@{ ViewBag.Title = "Index"; } <body> <user-app>Loading…</user-app> </body>
user-app
是AppComponent
的selector
,这就是我们在 HTML 中使用 Component 的方式- 接下来,在Solution Explorer中,双击systemjs.config.js,然后在底部,在packages部分添加main.js
- 运行项目,您应该会看到以下 Web 应用程序,显示Home页面和大图片。您可以在地址栏看到页面 URL 以home结尾,这与我们在 Home 页面的路由表中定义的 URL 相同。
- 到目前为止,我们已经使用 Angular2 在 ASP.NET MVC 应用程序中创建了基本架构,其中包含一个静态页面。下一步是创建用户管理页面,包括加载所有用户、添加新用户、更新和删除现有用户。
- 在用户管理页面中,我们将使用TypeScript Interface(用于用户模型)、Reactive forms和第三方组件 Ng2-Bs3-Modal 来进行模态弹出。
- 接下来,让我们创建用户interface。右键单击app文件夹,选择添加 -> 新建文件夹。将文件夹名称输入为Models
- 右键单击新创建的Models文件夹,选择添加 -> TypeScript 文件,将文件名输入为user.ts
- 在新创建的用户 interface 中输入以下变量
export interface IUser { Id: number, FirstName: string, LastName: string, Gender: string }
- 这些
interface
属性与数据库中的User
表相同。Angular2 的一个很棒之处在于,当我们将数据从数据库通过RESTful API加载时,用户对象会自动映射到IUser
interface 的数组。在接下来的步骤中,我们将看到这是如何实现的。 - 在继续
UserComponent
之前,让我们创建一些辅助文件,即全局
变量和枚举。我倾向于将所有端点、错误消息和其他共享变量保存在全局文件中,我将为 CRUD 操作创建一个枚举。右键单击app文件夹,选择添加 ->新建文件夹,命名文件夹为shared - 右键单击新创建的shared文件夹,选择添加 -> TypeScript 文件,输入名称为global.ts
- 将以下代码复制到global.ts
export class Global { public static BASE_USER_ENDPOINT = 'api/userapi/'; }
- 这是一个简单的
exportable
类,具有一个static
属性BASE_USER_ENDPOINT
,其中包含用户管理 RESTful API 的基本端点。 - 再次,右键单击shared文件夹,选择添加 -> TypeScript 文件,输入名称为enum.ts
- 在enum.ts文件中输入以下代码
export enum DBOperation { create = 1, update = 2, delete =3 }
- 枚举本身就说明了问题。与其使用硬编码的字符串进行 CRUD 操作(“
create
”、“update
”、“delete
”),不如使用DBOperation
枚举。 - 接下来,让我们使用 Angular2 HTTP服务创建调用 ASP.NET RESTful Web API 的重要函数,用于用户管理。正如我们在前面的步骤中所讨论的,我们将为 RESTful 用户 API 创建
GET
、POST
、PUT
和DELETE
请求,这些 API 我们已经通过 ASP.NET MVC Web API 在早期步骤中创建了。右键单击app文件夹,选择添加 -> 新建文件夹,输入名称为Service
。 - 右键单击新创建的Service文件夹,选择添加 -> TypeScript 文件,输入名称为user.service.ts
- 将以下代码复制到user.service.ts文件中
import { Injectable } from '@angular/core'; import { Http, Response, Headers, RequestOptions} from '@angular/http'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/do'; import 'rxjs/add/operator/catch'; @Injectable() export class UserService { constructor(private _http: Http) { } get(url: string): Observable<any> { return this._http.get(url) .map((response: Response) => <any>response.json()) // .do(data => console.log("All: " + JSON.stringify(data))) .catch(this.handleError); } post(url: string, model: any): Observable<any> { let body = JSON.stringify(model); let headers = new Headers({ 'Content-Type': 'application/json' }); let options = new RequestOptions({ headers: headers }); return this._http.post(url, body, options) .map((response: Response) => <any>response.json()) .catch(this.handleError); } put(url: string, id: number, model: any): Observable<any> { let body = JSON.stringify(model); let headers = new Headers({ 'Content-Type': 'application/json' }); let options = new RequestOptions({ headers: headers }); return this._http.put(url+id, body, options) .map((response: Response) => <any>response.json()) .catch(this.handleError); } delete(url: string, id: number): Observable<any> { let headers = new Headers({ 'Content-Type': 'application/json' }); let options = new RequestOptions({ headers: headers }); return this._http.delete(url+id,options) .map((response: Response) => <any>response.json()) .catch(this.handleError); } private handleError(error: Response) { console.error(error); return Observable.throw(error.json().error || 'Server error'); } }
为了理解上面的代码,我们需要学习
Observable
。您可以通过在 Google 上搜索轻松获取有关它的信息,但我建议您快速浏览以下链接: https://scotch.io/tutorials/angular-2-http-requests-with-observables -
简单来说,
Observable
更像数据流,与promise
方法(在 Angular 1.x 中)相反,Observable
不会一次性返回响应,而是以流的形式返回。它提供了非常有用的方法,例如map
(用于将结果映射到 interface)、filter
(从数据数组中过滤特定记录)等。Observable
还提供HTTP请求处理。Rxjs
是一个强大的库,为我们提供了所有Observable
方法。 -
第一个方法是
get
,它将 RESTful API URL 作为参数,并返回Observable<any>
。您也可以指定要返回的interface
的具体类型,例如Observable<IUser[]>
,但我倾向于保持通用。在接下来的行中,通过提供输入的 RESTful API 用户来调用 httpget
方法,然后调用map
方法将 JSON 响应映射到any
类型。您可以指定具体类型,例如<IUser[]>response.json()
。any
类型就像 C# 中的dynamic
,它执行编译时类型检查。 - RESTful API 的一个很棒之处在于 HTTP 动词就像函数名一样。也就是说,如果函数名以
GET
、PUT
、POST
或DELETE
开头,我们只需要基本 URL(端点)。通过 HTTP 调用,它会自动确定相应的函数。显而易见,一个 Web API 控制器应该有一个 HTTP 动词方法。 - 其他方法
POST
、PUT
和DELETE
具有几乎相同的函数体,它们创建 http 头并将IUser
interface 发送到正文。在 Web API 控制器函数中接收时,由于列名匹配,它会自动转换为user
实体。 - 现在我们已经创建了
user
服务,让我们将其添加到AppModule
。双击app文件夹中的app.module.ts文件进行编辑。通过添加以下行导入UserService
import { UserService} from './Service/user.service'
- 在
AppModule
的 providers 部分添加UserService
。 - 之后,让我们创建
UserComponent
。右键单击Components文件夹,选择添加 -> TypeScript 文件 - 将其命名为user.component.ts
- 我们将把
template
创建在单独的 HTML 文件中。因此,再次右键单击Components文件夹,选择添加-> HTML Page - 将其命名为user.component.html
- 在进入
UserComponent
之前,让我们配置一个用于模态弹出的第三方组件 ng2-bs3-modal。它非常易于使用。 - 双击
Angular2MVC
项目中的Package.json文件,并在devDependencies
部分添加以下包"ng2-bs3-modal": "0.10.4"
- 现在让我们从 NPM 下载这个包。右键单击package.json,然后选择还原包
- 双击Angular2MVC项目中的systemjs.config.js
- 在map部分添加以下文本
'ng2-bs3-modal': 'npm:/ng2-bs3-modal'
-
在 packages 部分添加以下文本
'ng2-bs3-modal': { main: '/bundles/ng2-bs3-modal.js', defaultExtension: 'js' }
- 最终更新应如下所示
- 现在我们有了模态弹出窗口,让我们创建
UserComponent
,它将包含查看所有用户、添加新用户、编辑和删除现有用户的功能。双击app -> components文件夹中的user.component.ts文件进行编辑 - 首先添加以下
import
语句import { Component, OnInit, ViewChild } from '@angular/core'; import { UserService } from '../Service/user.service'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { ModalComponent } from 'ng2-bs3-modal/ng2-bs3-modal'; import { IUser } from '../Models/user'; import { DBOperation } from '../Shared/enum'; import { Observable } from 'rxjs/Rx'; import { Global } from '../Shared/global';
- 我们导入了
Components
、OnInit
(以使用OnInit
事件)、ViewChild
(以访问 Modal 弹出窗口的属性)。 - 然后我们导入执行 HTTP 调用到服务器的
UserService
。 - 在
UserComponent
中,我们将使用Reactive
(Model-driven)表单,我觉得它比 template driven forms 更整洁、更易于使用。对我来说,它看起来像一个强类型的 ASP.NET MVC razor 视图,并且对unit testing
也很好。表单字段、验证和验证错误可以在 TypeScript 端进行管理,而 HTML 视图只有最少的表单逻辑,这是将代码放在一个地方的好习惯。要阅读更多关于 Reactive form 的信息,请单击此处。 ModalComponent
是我们之前下载的第三方模态弹出窗口。IUser
是我们用作 Model 来存储用户信息interface
。DBOperation
和Global
是枚举和全局变量。Observable
,我们在前几步中简要讨论过。我们将使用Rxjs
库中的subscribe
和filter
函数。- 接下来,复制以下
Component
元数据信息,放在import
语句下方@Component({ templateUrl: 'app/Components/user.component.html' })
- 由于
User
是一个父组件,并且我们不会在任何其他组件中使用它,所以我们不指定Selector
属性。User 组件的 HTML 将在user.component.html文件中。 - 接下来,让我们开始
UserComponent
类主体并声明必需的变量export class UserComponent implements OnInit { @ViewChild('modal') modal: ModalComponent; users: IUser[]; user: IUser; msg: string; indLoading: boolean = false; userFrm: FormGroup; dbops: DBOperation; modalTitle: string; modalBtnTitle: string; }
- 我们以
export
开始我们的类,然后是UserComponent
名称。由于我们将使用onInit
事件,我们的类必须实现它。 - 下一行以
@ViewChild(‘modal’)
开头。modal
是我们将在 HTML 模板中创建的 Modal 弹出窗口组件的占位符。这是如果您想在 TypeScript 中访问任何 HTML 元素的语法。:ModalComponent
指定元素的类型。 - 接下来,我们正在创建一个
IUser
interface 的数组来保存用户列表,以及一个 User 来保存一个用户信息,用于添加、编辑和删除。其他是一些字符串和布尔变量,我们将在接下来的步骤中使用它们来显示一些消息。 - 正如我们在前面的步骤中讨论过的,我们将使用
Reactive
(Model-driven)表单,所以我们创建了userform
,类型为Formgroup
。 - 接下来是添加
UserComponent
的constructor
constructor(private fb: FormBuilder, private _userService: UserService) { }
- Angular2 的一个很棒之处在于依赖注入。在构造函数中,您可以看到,我们通过 DI 获取了
FormBuilder
和UserService
的实例。要阅读更多关于 DI 的信息,请单击此处。 - 到目前为止,我们的
UserComponent
应该如下所示 - 此时,您可能会遇到错误,因为仍然没有实现
ngOnInit
事件。让我们继续添加ngOnInit
事件,我们将创建并初始化我们的 Reactive User 表单ngOnInit(): void { this.userFrm = this.fb.group({ Id: [''], FirstName: ['', Validators.required], LastName: [''], Gender: [''] }); this.LoadUsers(); }
- 我们正在初始化 User 表单,指定表单元素和验证规则。目前,表单初始化为空字符串‘’。
- 接下来,让我们创建
LoadUsers
方法。顾名思义,此方法将调用UserService
中的get
方法来通过 RESTful API 从数据库加载所有用户LoadUsers(): void { this.indLoading = true; this._userService.get(Global.BASE_USER_ENDPOINT) .subscribe(users => { this.users = users; this.indLoading = false; }, error => this.msg = <any>error); }
-
Subscribe
是Observable
的一部分,我们在前面的步骤中讨论过。一旦用户加载完成,它就会将其保存在users
变量中。如果发生任何错误,错误消息将保存在msg
变量中。indLoading
是我们在这里使用的布尔变量,用于在完整响应加载之前显示加载消息。 -
接下来,让我们添加三个方法来显示用于
添加
、更新
和删除
用户的模态弹出窗口。为这些函数添加以下代码addUser() { this.dbops = DBOperation.create; this.SetControlsState(true); this.modalTitle = "Add New User"; this.modalBtnTitle = "Add"; this.userFrm.reset(); this.modal.open(); } editUser(id: number) { this.dbops = DBOperation.update; this.SetControlsState(true); this.modalTitle = "Edit User"; this.modalBtnTitle = "Update"; this.user = this.users.filter(x => x.Id == id)[0]; this.userFrm.setValue(this.user); this.modal.open(); } deleteUser(id: number) { this.dbops = DBOperation.delete; this.SetControlsState(false); this.modalTitle = "Confirm to Delete?"; this.modalBtnTitle = "Delete"; this.user = this.users.filter(x => x.Id == id)[0]; this.userFrm.setValue(this.user); this.modal.open(); }
-
所有这些方法都相似。让我们以
AddUser
方法为例并对其进行解释。首先,我们将当前 DB 操作存储在dpops
变量中,该变量的类型为DBOperation
枚举。接下来,我们调用SetControlsState
方法,该方法将启用或禁用表单控件。接下来的变量设置模态弹出窗口的标题和按钮文本。仅在AddUser
函数中,我们才重置表单以清除它。接下来,我们调用modal.open()
函数来显示模态弹出窗口。在编辑和删除用户方法中,我们以参数形式获取UserID
,调用Observable
的 filter 方法从用户列表中获取单个用户。filter 语法类似于 C# 中的匿名方法。下一行是将单个用户分配给 user 表单,这将为前端设置值,轻而易举。 -
让我们创建
SetControlsState
,它将启用或禁用表单。Reactive
表单具有enable
和disable
方法,这些方法可以使控件只读和可编辑。SetControlsState(isEnable: boolean) { isEnable ? this.userFrm.enable() : this.userFrm.disable(); }
-
下一个方法是
onSubmit
,它实际上获取表单值,并根据DBOperation
枚举值执行添加、更新和删除操作。我们使用简单的switch
语句。粘贴以下代码onSubmit(formData: any) { this.msg = ""; switch (this.dbops) { case DBOperation.create: this._userService.post(Global.BASE_USER_ENDPOINT, formData._value).subscribe( data => { if (data == 1) //Success { this.msg = "Data successfully added."; this.LoadUsers(); } else { this.msg = "There is some issue in saving records, please contact to system administrator!" } this.modal.dismiss(); }, error => { this.msg = error; } ); break; case DBOperation.update: this._userService.put(Global.BASE_USER_ENDPOINT, formData._value.Id, formData._value).subscribe( data => { if (data == 1) //Success { this.msg = "Data successfully updated."; this.LoadUsers(); } else { this.msg = "There is some issue in saving records, please contact to system administrator!" } this.modal.dismiss(); }, error => { this.msg = error; } ); break; case DBOperation.delete: this._userService.delete(Global.BASE_USER_ENDPOINT, formData._value.Id).subscribe( data => { if (data == 1) //Success { this.msg = "Data successfully deleted."; this.LoadUsers(); } else { this.msg = "There is some issue in saving records, please contact to system administrator!" } this.modal.dismiss(); }, error => { this.msg = error; } ); break; } }
-
代码非常简单且自明。一旦我们提交表单,它就会发送所有值,我们可以通过
.value
属性获取这些值。TypeScript 部分就到此为止。 -
让我们为
UserComponent
编写 HTML 模板。双击user.component.html进行编辑 -
将以下代码复制到user.component.html
<div class='panel panel-primary'> <div class='panel-heading'> User Management </div> <div class='panel-body'> <div class='table-responsive'> <div style="padding-bottom:10px"><button class="btn btn-primary" (click)="addUser()">Add</button></div> <div class="alert alert-info" role="alert" *ngIf="indLoading"><img src="../../images/loading.gif" width="32" height="32" /> Loading...</div> <div *ngIf='users && users.length==0' class="alert alert-info" role="alert">No record found!</div> <table class='table table-striped' *ngIf='users && users.length'> <thead> <tr> <th>First Name</th> <th>Last Name</th> <th>Gender</th> <th></th> </tr> </thead> <tbody> <tr *ngFor="let user of users"> <td>{{user.FirstName}}</td> <td>{{user.LastName}}</td> <td>{{user.Gender}}</td> <td> <button title="Edit" class="btn btn-primary" (click)="editUser(user.Id)">Edit</button> <button title="Delete" class="btn btn-danger" (click)="deleteUser(user.Id)">Delete</button> </td> </tr> </tbody> </table> <div> </div> </div> <div *ngIf="msg" role="alert" class="alert alert-info alert-dismissible"> <button type="button" class="close" data-dismiss="alert" aria-label="Close"> <span aria-hidden="true">×</span></button> <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> <span class="sr-only">Error:</span> {{msg}} </div> </div> </div> <modal #modal> <form novalidate (ngSubmit)="onSubmit(userFrm)" [formGroup]="userFrm"> <modal-header [show-close]="true"> <h4 class="modal-title">{{modalTitle}}</h4> </modal-header> <modal-body> <div class="form-group"> <div> <span>Full name*</span> <input type="text" class="form-control" placeholder="First Name" formControlName="FirstName"> </div> <div> <span>Full name</span> <input type="text" class="form-control" placeholder="Last Name" formControlName="LastName"> </div> <div> <span>Gender*</span> <select formControlName="Gender" class="form-control"> <option>Male</option> <option>Female</option> </select> </div> </div> </modal-body> <modal-footer> <div> <a class="btn btn-default" (click)="modal.dismiss()">Cancel</a> <button type="submit" [disabled]="userFrm.invalid" class="btn btn-primary">{{modalBtnTitle}}</button> </div> </modal-footer> </form> </modal>
-
如果您查看添加按钮,我们正在使用
(click)
函数调用AddUser
函数,这是我们之前讨论过的event
绑定示例。 -
接下来,我们使用
*ngIf
,即structural directives
,根据indLoading
布尔变量显示加载消息。 -
接下来,我们使用
*ngFor
structural directive 来遍历users
数组并显示用户信息。 -
下一个代码是用于模态弹出窗口的。您可以看到
#modal
占位符,我们将在 TypeScript 端使用它通过@ViewChild
来访问 open 和 dismiss 函数。 -
接下来,我们正在创建表单。
(ngSumbit)
事件会将表单数据发送到 TypeScriptonSumit
函数。 -
通过
[formgorup]
属性绑定,我们正在分配我们已在 TypeScript 端创建的userform
。我们通过formControlName
属性告诉模板相应的表单控件。 -
在表单有效之前,添加和编辑按钮将是禁用的。这由
[disabled]
属性绑定处理,直到userform.invalid
属性变为启用。 -
UserComponent
就到此为止了。现在,让我们为UserComponent
添加路由,并将其添加到AppModule
。 -
双击app文件夹中的app.routing.ts进行编辑
-
通过以下代码导入
UserComponent
import { UserComponent } from './components/user.component';
-
如下所示添加
UserComponent
路由{ path: 'user', component: UserComponent }
最终的app.routing.ts应如下所示
-
通过双击编辑app.component.ts文件
-
将 User Component 添加到 App Module
import { UserComponent } from './components/user.component';
-
在 declaration 部分添加
UserComponent
,最终的AppModule
应如下所示 -
添加用户管理的菜单项。双击app.component.ts并添加以下行
<li><a [routerLink]="['user']">Users Management</a></li>
-
最终的app.component.ts应如下所示
-
编译并运行应用程序。
历史
- 2017 年 4 月 16 日:创建
- 2017 年 5 月 21 日:添加了 第 2 部分
- 2017 年 8 月 12 日:附加了 Angular 4 解决方案