65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (159投票s)

2017 年 4 月 19 日

CPOL

28分钟阅读

viewsIcon

927595

downloadIcon

26448

本文将帮助初学者在 ASP.NET MVC 中集成 Angular2,在 ASP.NET MVC Web API 中创建 RESTful API,并在 Angular2 中构建前端。

系列文章

引言

这是一篇初学者级别的文章,写给有基本编程知识,希望从零开始学习 Angular2、C# 和 RESTful API 的新手开发者和学生。如果您是经验丰富的开发者,只需要概览,可以下载附件项目并自行查看。您需要 Visual Studio 和 SQL Server Management Studio 来配合本文开发项目并编译附件项目。

让我们开始...

设置 Angular2 环境

  1. 打开 Visual Studio。我使用的是 Visual Studio 2017 Community,您也可以使用安装了Node.jsTypeScript包的 Visual Studio 2015 Community。(阅读更多关于 Visual Studio 2015 Update 3 的信息。)
  2. 转到“文件”菜单,选择文件 -> 新建 -> 项目

  3. 输入项目名称并选择您想要的 .NET Framework(本文我使用的是.NET Framework 4.6)。点击确定按钮

  4. 在下一个屏幕上选择MVC,并勾选添加文件夹和核心引用选项中的Web API,因为我们将创建 RESTful API 进行 CRUD 操作。点击确定按钮

  5. 基本的 ASP.NET MVC 项目已创建。下一步是为 Angular2 应用程序准备它。我们将在接下来的步骤中进行。
  6. 右键单击项目Angular2MVC,选择添加 -> 新建项

  7. 在右上角的搜索框中输入package.jsonnpm 配置文件将被过滤出来。点击添加按钮将package.json添加到项目中

  8. 我们将使用NPM(Node 包管理器)配置文件来管理所有 Angular2 包。要阅读更多关于 NPM 的信息,请访问此链接
  9. 接下来,从 Angular2 Quick Start GitHub 链接复制 Package.json 并粘贴到 Angular2MVC 项目中新添加的package.json文件中

  10. package.json文件的 dependencies 部分,我们可以看到所有 Angular2 相关的包。要了解^~符号的区别,请在此处查看
  11. 右键单击package.json文件,选择还原包选项。Visual Studio 的Node.js工具将下载package.json文件中提到的所有依赖包。将来如果您需要任何额外的包,只需将其添加到DevDependencies部分并还原即可,这真的能让工作更轻松。

  12. 您会在项目中找到一个新文件夹node_modules,其中包含所有下载的包

  13. 下一步是让我们的项目知道如何获取这些包。我们将添加systemjs.config.js文件。右键单击Angular2MVC项目,选择添加 -> JavaScript 文件

  14. 项名称字段中输入systemjs.config.js,然后单击添加按钮

  15. 从 Angular2 Quick Start GitHub 复制systemjs.config.js文件的内容,并粘贴到 Angular2MVC 项目中新添加的systemjs.config.js文件中

  16. 接下来,我们通过右键单击Angular2MVC项目并选择添加 -> 新建项来添加TypeScript JSON 配置文件。选择TypeScript JSON 配置文件,然后单击添加按钮

  17. 从 Angular2 Quick Start GitHub 复制tsconfig.js文件的内容,并替换 Angular2MVC 项目中新添加的tsconfig.js文件

  18. 如果您在尝试构建时遇到编译错误,请不用担心。只要我们开始添加任何TypeScript 文件,这些错误就会消失。
  19. 现在我们在 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)
);

  1. 右键单击App_Data文件夹,选择添加 -> 新建项。在数据部分,您可以找到SQL Server 数据库选项。选择它并将其命名为UserDB

  2. 数据库创建后,双击UserDB.mdf数据库文件以打开

  3. 右键单击UserDB.mdf,选择新建查询。粘贴以下 SQL 查询以创建TblUser表,然后单击执行按钮创建表
  4. 右键单击文件夹,选择刷新选项

  5. 接下来,我们将开发ASP.NET MVC端,包括设置布局索引页面以加载 Angular2 主页,以及用于加载索引视图的 MVC 控制器,以及用于 RESTful CRUD(创建读取更新删除)用户 API 的 Web API 2.0 控制器。
  6. 首先,我们转到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 }
                            );
            }
        }
    }

  7. 接下来,让我们打开_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 模块工作流。有关更多信息,请单击此处

  8. 最终的_Layout.cshtml应该如下所示

  9. 接下来,让我们为UserDB数据库创建ADO.NET Entity Data Model。右键单击Angular2MVC项目,选择添加 -> 新建文件夹,将名称指定为DBContext或您想要的任何名称

  10. 右键单击新创建的DBContext文件夹,选择添加 -> 新建项

  11. 在左侧面板的Visual C#下,选择数据。在右侧,选择ADO.NET Entity Data Model。输入名称UserDBEntities或您选择的任何名称。单击添加按钮。

  12. 在下一个屏幕上,选择EF Designer for Data,然后单击Next按钮

  13. 在下一个屏幕上单击新建连接按钮

  14. 在下一个屏幕上,如果数据源未选择为Microsoft SQL Server Database file (SqlClient),请单击更改按钮并选择它:

  15. 数据库文件名中,单击浏览按钮:
  16. 浏览到前面步骤中创建并保存在App_Data文件夹中的UserDB数据库,然后单击选择 SQL Server 数据库文件连接属性窗口上的确定按钮

  17. 勾选将连接设置保存在 Web.Config 中复选框,然后单击Next按钮

  18. 在下一个屏幕上,您可以选择实体框架版本。我使用的是6.x,您可以根据自己的选择使用

  19. 在下一个屏幕上,单击复选框。您将只看到一个表TblUser。单击Finish按钮以结束向导

  20. 这需要几秒钟,最后,您将看到我们的数据库实体模型,其中包含一个表

  21. 此时,如果您尝试编译项目,可能会遇到很多TypeScript错误。为了解决这个问题,请创建一个名为app的文件夹,右键单击它,选择添加 -> TypeScript 文件

  22. 输入名称main.ts并单击OK(我们将在接下来的步骤中使用此文件)。现在重新生成项目,它应该会成功生成。

开发用户管理 RESTful API

  1. 下一步是创建用于读取添加更新删除用户的ASP.NET MVC Web API
  2. 首先,我们将创建父 API 控制器,它将包含所有 API 控制器共享的通用方法。目前,我们将只有一个方法可以将类对象序列化为 JSON 字符串,以供 Angular2 前端使用,并用于UserDB数据库DBContext对象,以便在子控制器中执行数据库操作。右键单击Controllers文件夹,选择添加 -> Controller…

  3. 选择Web API 2 Controller – Empty,然后单击添加按钮

  4. 输入名称BaseAPIController,然后单击添加按钮

  5. 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;
        }
  6. 上面提供的代码解释得很清楚,我们正在创建一个名为UserDBUserDBEntities类对象,通过它可以调用加载添加更新删除用户的方法。ToJson方法接收任何类型的类对象,创建一个带有OK HttpStatusCodeHTTP 响应对象,并通过调用Newtonsoft.json库中的JsonConvert方法将对象序列化为 JSON 字符串。最终代码应如下所示

  7. 接下来,让我们为用户管理创建RESTful Web API,即从数据库加载所有用户、添加新用户、更新和删除现有用户。我们将创建以下方法
    1. 用于读取所有用户的GET方法
    2. 用于创建新用户的POST方法
    3. 用于更新现有用户的PUT方法
    4. 用于删除现有用户的DELETE方法
  8. 要阅读更多关于HTTP Verbs和方法的信息,请单击此处
  9. 右键单击Controllers文件夹,选择添加 -> Controller…

  10. 选择Web API 2 Controller – Empty,然后单击添加按钮

  11. 输入名称UserAPIController,然后单击添加按钮

  12. 用以下代码替换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());
            }
        }
    1. UserAPIController继承自BaseAPIController,以使用UserDB对象和ToJson方法将User实体转换为 JSON 字符串并将其保存在 HTTP 响应消息中。
    2. Get(): 从数据库加载所有用户,并返回包含转换为JSON字符串的用户实体的 HTTP 响应消息。
    3. Post([FromBody]TblUser value): 从前端接收用户信息并将其保存到数据库。成功保存则返回1
    4. Put(int id, [FromBody]TblUser value): 接收现有用户 ID 和更新的信息,并将其更新到数据库。成功更新则返回1
    5. Delete(int id): 接收现有用户 ID,按 ID 加载用户并删除它。成功删除则返回1
  13. 最终的UserAPIController类应如下所示

开发 Angular2 应用程序

  1. 现在让我们开始激动人心的部分,编写Angular2代码。在实际编写代码之前,理解Angular2架构非常重要。由于我不会专注于编写 Angular2 的内容,因为您可以找到大量的教程和免费视频,让我们回顾一下Angular2的基本结构,如果您懒得去 angular.io 网站,可以从这里获取
    1. Modules: 每个 Angular 应用至少有一个 Angular 模块类,即模块。应用程序通过引导其根模块来启动。在开发过程中,您很可能会在main.ts文件中引导AppModule,我们将在后续步骤中创建该文件。根模块通常命名为AppModule。在AppModule中,我们指定应用程序使用的所有组件服务或自定义管道
    2. Components: 组件控制屏幕上的视图,您可以定义属性方法来控制视图。如果您曾经使用过 ASP.NET 窗体,我会说组件就像代码隐藏文件aspx.cs文件,您可以通过方法和属性与 aspx 文件进行交互。
    3. Templates: 您通过伴随的模板定义组件的视图。模板是 HTML 的一种形式,它告诉 Angular 如何渲染组件。根据我前面步骤的例子,它就像 ASP.NET 窗体中的aspx文件。
    4. Metadata: 元数据告诉 Angular 如何处理一个类。如果您看到一个组件,它只是一个类,元数据告诉它与该组件关联的模板(代码隐藏或 HTML)、任何样式表或如何通过Selector属性使用该组件。
    5. Data binding: 简单来说,信息或控件如何在模板组件之间传输。例如,当您单击模板中的任何按钮时,如何获取组件中的click事件并执行您的逻辑。Angular2 提供以下类型的数据绑定
      1. {{}} interpolation 显示组件中声明的任何变量值。
      2. [ ] property binding 用于将值从父组件发送到子组件。我们将在未来的章节中使用它。
      3. ( ) event binding 用于将任何事件从模板捕获到组件,例如 (click)。
    6. Directives: 有两种指令:结构性指令和属性指令。
      1. 结构性指令通过在 DOM 中添加、删除和替换元素来改变布局,例如,*ngFor*ngIf用于遍历 HTML 元素并显示/隐藏元素。
      2. 属性指令改变现有元素的外观或行为。在模板中,它们看起来像常规的 HTML 属性,因此得名,例如,ngStyle用于样式表,ngModel用于双向数据绑定。
    7. Services: 服务是一个广泛的类别,涵盖了您的应用程序所需的任何值、函数或功能。关于服务,Angular2 没有特定之处。Angular2 没有服务的定义。没有服务基类,也没有注册服务的地方。服务的例子包括ErrorLogHTTP等。Component应仅充当模板和用户之间的协调者角色。它应该将其余功能,例如从服务器获取数据、删除或更新、日志记录、显示错误等委托给服务。
    8. Dependency injection: 依赖注入是一种为类提供完全满足其所需依赖关系的新实例的方法。大多数依赖项都是服务。Angular2 使用依赖注入为新组件提供它们所需的服务。例如,对于HTTP服务,我们将在后续步骤中使用依赖注入将服务实例提供给组件。
    9. 有关更多详细信息和更好的理解,请单击此处
  2. 希望您已经对 Angular2 架构有了基本了解。让我们使用 ASP.NET MVC 中的 Angular 2 和 RESTFul API 作为后端服务,创建一个用户管理页面(添加更新删除查看用户)。
  3. 在我们的项目中,我们将按照约定在app文件夹中创建所有 Angular2 相关代码。如果您还没有创建app文件夹,请继续创建。如果您遵循了前面的步骤,应该会在app文件夹中有一个名为main.ts的 TypeScript 文件,我们将使用它来引导AppModule

  4. 在继续之前,让我展示一下最终的应用程序将是什么样子。它将有两个页面:一个是只有大图片的首页,另一个是带有用户信息的表格视图,每个记录旁边都有编辑和删除按钮,以及表格顶部的添加按钮以添加新用户。每个按钮都会打开一个模态弹出窗口,您可以在其中执行相应的功能。以下是两个页面和每个功能的屏幕截图
  5. 现在您对最终应用程序有了基本了解。让我们开始开发应用程序的 Angular2 部分。让我们牢记 Angular2 架构,并创建应用程序的基本架构。
  6. 首先,让我们创建 Angular2 模块,它将是应用程序的入口点。右键单击app文件夹,选择添加 -> TypeScript 文件
  7. 如果您在第二个菜单中看不到TypeScript 文件,请右键单击app文件夹,选择添加 -> 新建项,搜索TypeScript,然后选择TypeScript 文件,输入名称并选择OK按钮
  8. 将新 TypeScript 文件命名为app.module.ts,然后单击OK按钮
  9. 在新添加的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 { }
  10. 只是为了刷新您的记忆,我们正在使用TypeScript和 Angular2。如果您想了解更多关于 TypeScript 的信息,请单击此处
  11. 如果您快速浏览AppModule类,您可以看到我们正在导入所需的库,例如 Angular Core 中的NgModule。同样,我们将使用Reactive forms来处理用户,我们从 Angular Forms 包中导入ReactiveFormModule。我们将继续扩展app.module.ts文件,添加用户组件、服务、模态弹出窗口等。
    1. NgModule元数据部分
      1. Imports 包含模块列表。
      2. Declarations 包含组件列表,我们将在下一步添加用户组件。
      3. Providers 包含服务列表。我们将添加一个具有HTTP操作的服务来执行用户读取、添加、更新和删除操作。目前,它具有基本的href路径。
      4. Bootstrap 包含入口组件,我们将在下一步创建app.component.ts文件并在此处添加它。
  12. 下一步是编辑app文件夹中的main.ts文件,并在其中添加以下代码
    import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
    import { AppModule } from './app.module';
    platformBrowserDynamic().bootstrapModule(AppModule);

  13. main.ts代码解释得很清楚。AppModule引用是从当前文件夹导入的,使其成为入口模块,并使用platformBrowserDynamic模块的bootstrapModule函数加载其他辅助资源(引导)。Bootstrap函数初始化 Angular2 应用程序,加载所需的组件、服务或其他辅助资源以运行应用程序。尝试构建项目以避免在后续步骤中出现任何错误。
  14. 接下来,创建两个 TypeScript 文件,app.component.tsapp.routing.ts,用于主应用程序组件和路由表。我们稍后会回来处理它们。右键单击app文件夹,选择添加 -> TypeScript 文件

  15. 输入名称app.component.ts,然后单击OK按钮

  16. 再次单击app文件夹,选择添加 -> TypeScript 文件,输入名称app.routing.ts,然后单击OK按钮

  17. 接下来,让我们创建只有一张大图片的Home组件

  18. 我们将在新文件夹中创建所有用户组件。右键单击app文件夹,选择添加 -> 新建文件夹,输入名称Components

  19. 右键单击新创建的Component文件夹,选择添加 -> TypeScript 文件

  20. 输入名称home.component.ts,然后单击OK按钮

  21. 在新创建的home.component.ts文件中添加以下代码
    import { Component } from "@angular/core";
    
    @Component({
    template: `<img src="../../images/users.png" style="text-align:center"/>`
    })
    
    export class HomeComponent{
    }

  22. HomeComponent中,您可以在元数据的 template 属性中看到,我们有一个来自根 images 文件夹的纯 HTML 图像元素,它将在屏幕上显示users.png。您可以获取任何图片,将其保存在images文件夹中,然后在HomeComponent中加载。
  23. 右键单击Angular2MVC项目,选择添加 -> 新建文件夹,输入文件夹名称为images

  24. 右键单击新添加的images文件夹,选择在文件资源管理器中打开,将下面提供的图像复制到打开的位置

  25. 我们的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);

  26. 在上面的代码中,我们从 angular router 包导入了路由库,并从components文件夹导入了最近创建的HomeComponent。在 app routers 中,path属性是浏览器地址栏中可见的实际 URL,例如 https://:4500/home。一旦我们创建了UserComponent,我们将为其添加另一个路由。
  27. 接下来双击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 {
     
    }

  28. AppComponent很简单,它有一个包含已知引导代码的模板,用于创建只有一个 home 链接的导航栏。Home 页面的 routerlink home 名称是我们之前在app.routing.ts路由表中定义的。您可以根据需要定义任何名称,例如 default、index 等。router-outlet充当动态加载的视图组件的占位符。我们还在AppComponent元数据部分定义了selector属性(user-app),因为我们将在 MVC 视图(index.cshtml)中使用此selector来引导AppComponent并加载它。有关router-outlet的更多信息,请单击此处
  29. 所以我们已经创建了应用程序组件(AppComponent),让我们转到AppModule并 along路由表注册HomeComponentAppComponent。之后,我们将添加AppComponent进行引导。要完成所有这些,请按照以下方式更新您的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';
    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 { }

  30. 您可以看到我们导入了HomeComponentAppComponent,将它们添加到 declaration 中,并将AppComponent作为应用程序的入口点进行引导。(引导不仅仅是一个入口点,正如我们在前面的步骤中讨论过的,您可以搜索 Google 以完全理解它。这里为了简单起见,我只将其称为入口点)。
  31. 我们几乎快要运行我们的 Angular2 应用程序并查看主页了。转到Views -> Home,然后双击Index.cshtml进行编辑

  32. 删除现有代码,并输入以下代码行
    @{
        ViewBag.Title = "Index";
    }
    
    <body>
        <user-app>Loading…</user-app>
    </body>

  33. user-appAppComponentselector,这就是我们在 HTML 中使用 Component 的方式

  34. 接下来,在Solution Explorer中,双击systemjs.config.js,然后在底部,在packages部分添加main.js

  35. 运行项目,您应该会看到以下 Web 应用程序,显示Home页面和大图片。您可以在地址栏看到页面 URL 以home结尾,这与我们在 Home 页面的路由表中定义的 URL 相同。

  36. 到目前为止,我们已经使用 Angular2 在 ASP.NET MVC 应用程序中创建了基本架构,其中包含一个静态页面。下一步是创建用户管理页面,包括加载所有用户、添加新用户、更新和删除现有用户。

  37. 在用户管理页面中,我们将使用TypeScript Interface(用于用户模型)、Reactive forms和第三方组件 Ng2-Bs3-Modal 来进行模态弹出。
    1. Interface: Interface 是一个抽象类型,它不包含任何代码,不像类。它只定义了一个 API 的签名或形状,这就是为什么我们将使用 interface 来定义我们的用户模型。
    2. Reactive Forms: Angular2 提供两种表单:Template driven 和Reactive Forms(Model Driven Forms)。有一篇关于这两种表单的精彩文章,地址是这里 & 这里。如果您是 ASP.NET MVC 开发人员,Reactive Forms 就像 MVC 的强类型Razor视图。
  38. 接下来,让我们创建用户interface。右键单击app文件夹,选择添加 -> 新建文件夹。将文件夹名称输入为Models

  39. 右键单击新创建的Models文件夹,选择添加 -> TypeScript 文件,将文件名输入为user.ts

  40. 在新创建的用户 interface 中输入以下变量
    export interface IUser {
        Id: number,
        FirstName: string,
        LastName: string,
        Gender: string
    }

  41. 这些interface属性与数据库中的User表相同。Angular2 的一个很棒之处在于,当我们将数据从数据库通过RESTful API加载时,用户对象会自动映射到IUser interface 的数组。在接下来的步骤中,我们将看到这是如何实现的。
  42. 在继续UserComponent之前,让我们创建一些辅助文件,即全局变量和枚举。我倾向于将所有端点、错误消息和其他共享变量保存在全局文件中,我将为 CRUD 操作创建一个枚举。右键单击app文件夹,选择添加 ->新建文件夹,命名文件夹为shared

  43. 右键单击新创建的shared文件夹,选择添加 -> TypeScript 文件,输入名称为global.ts

  44. 将以下代码复制到global.ts
    export class Global {
        public static BASE_USER_ENDPOINT = 'api/userapi/';
    }

  45. 这是一个简单的exportable类,具有一个static属性BASE_USER_ENDPOINT,其中包含用户管理 RESTful API 的基本端点。
  46. 再次,右键单击shared文件夹,选择添加 -> TypeScript 文件,输入名称为enum.ts

  47. enum.ts文件中输入以下代码
    export enum DBOperation {
        create = 1,
        update = 2,
        delete =3
    }

  48. 枚举本身就说明了问题。与其使用硬编码的字符串进行 CRUD 操作(“create”、“update”、“delete”),不如使用DBOperation枚举。
  49. 接下来,让我们使用 Angular2 HTTP服务创建调用 ASP.NET RESTful Web API 的重要函数,用于用户管理。正如我们在前面的步骤中所讨论的,我们将为 RESTful 用户 API 创建GETPOSTPUTDELETE请求,这些 API 我们已经通过 ASP.NET MVC Web API 在早期步骤中创建了。右键单击app文件夹,选择添加 -> 新建文件夹,输入名称为Service

  50. 右键单击新创建的Service文件夹,选择添加 -> TypeScript 文件,输入名称为user.service.ts

  51. 将以下代码复制到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

  52. 简单来说,Observable更像数据流,与promise方法(在 Angular 1.x 中)相反,Observable不会一次性返回响应,而是以流的形式返回。它提供了非常有用的方法,例如map(用于将结果映射到 interface)、filter(从数据数组中过滤特定记录)等。Observable还提供HTTP请求处理。Rxjs是一个强大的库,为我们提供了所有Observable方法。

  53. 第一个方法是get,它将 RESTful API URL 作为参数,并返回Observable<any>。您也可以指定要返回的interface的具体类型,例如Observable<IUser[]>,但我倾向于保持通用。在接下来的行中,通过提供输入的 RESTful API 用户来调用 http get方法,然后调用map方法将 JSON 响应映射到any类型。您可以指定具体类型,例如<IUser[]>response.json()any类型就像 C# 中的dynamic,它执行编译时类型检查。

  54. RESTful API 的一个很棒之处在于 HTTP 动词就像函数名一样。也就是说,如果函数名以GETPUTPOSTDELETE开头,我们只需要基本 URL(端点)。通过 HTTP 调用,它会自动确定相应的函数。显而易见,一个 Web API 控制器应该有一个 HTTP 动词方法。
  55. 其他方法POSTPUTDELETE具有几乎相同的函数体,它们创建 http 头并将IUser interface 发送到正文。在 Web API 控制器函数中接收时,由于列名匹配,它会自动转换为user实体。
  56. 现在我们已经创建了user服务,让我们将其添加到AppModule。双击app文件夹中的app.module.ts文件进行编辑。通过添加以下行导入UserService
    import { UserService} from './Service/user.service'
  57. AppModule的 providers 部分添加UserService

  58. 之后,让我们创建UserComponent。右键单击Components文件夹,选择添加 -> TypeScript 文件

  59. 将其命名为user.component.ts

  60. 我们将把template创建在单独的 HTML 文件中。因此,再次右键单击Components文件夹,选择添加-> HTML Page

  61. 将其命名为user.component.html

  62. 在进入UserComponent之前,让我们配置一个用于模态弹出的第三方组件 ng2-bs3-modal。它非常易于使用。
  63. 双击Angular2MVC项目中的Package.json文件,并在devDependencies部分添加以下包
    "ng2-bs3-modal": "0.10.4"

  64. 现在让我们从 NPM 下载这个包。右键单击package.json,然后选择还原包

  65. 双击Angular2MVC项目中的systemjs.config.js
  66. map部分添加以下文本
    'ng2-bs3-modal': 'npm:/ng2-bs3-modal'
  67. 在 packages 部分添加以下文本

               'ng2-bs3-modal':
               { main: '/bundles/ng2-bs3-modal.js', defaultExtension: 'js' }
  68. 最终更新应如下所示

  69. 现在我们有了模态弹出窗口,让我们创建UserComponent,它将包含查看所有用户、添加新用户、编辑和删除现有用户的功能。双击app -> components文件夹中的user.component.ts文件进行编辑

  70. 首先添加以下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';
  71. 我们导入了ComponentsOnInit(以使用OnInit事件)、ViewChild(以访问 Modal 弹出窗口的属性)。
  72. 然后我们导入执行 HTTP 调用到服务器的UserService
  73. UserComponent中,我们将使用Reactive(Model-driven)表单,我觉得它比 template driven forms 更整洁、更易于使用。对我来说,它看起来像一个强类型的 ASP.NET MVC razor 视图,并且对unit testing也很好。表单字段、验证和验证错误可以在 TypeScript 端进行管理,而 HTML 视图只有最少的表单逻辑,这是将代码放在一个地方的好习惯。要阅读更多关于 Reactive form 的信息,请单击此处
  74. ModalComponent是我们之前下载的第三方模态弹出窗口。
  75. IUser是我们用作 Model 来存储用户信息interface
  76. DBOperationGlobal是枚举和全局变量。
  77. Observable,我们在前几步中简要讨论过。我们将使用Rxjs库中的subscribefilter函数。
  78. 接下来,复制以下Component元数据信息,放在import语句下方
    @Component({
        
    templateUrl: 'app/Components/user.component.html'
    
    })
  79. 由于User是一个父组件,并且我们不会在任何其他组件中使用它,所以我们不指定Selector属性。User 组件的 HTML 将在user.component.html文件中。
  80. 接下来,让我们开始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;
    }
  81. 我们以export开始我们的类,然后是UserComponent名称。由于我们将使用onInit事件,我们的类必须实现它。
  82. 下一行以@ViewChild(‘modal’)开头。modal是我们将在 HTML 模板中创建的 Modal 弹出窗口组件的占位符。这是如果您想在 TypeScript 中访问任何 HTML 元素的语法。:ModalComponent指定元素的类型。
  83. 接下来,我们正在创建一个IUser interface 的数组来保存用户列表,以及一个 User 来保存一个用户信息,用于添加、编辑和删除。其他是一些字符串和布尔变量,我们将在接下来的步骤中使用它们来显示一些消息。
  84. 正如我们在前面的步骤中讨论过的,我们将使用Reactive(Model-driven)表单,所以我们创建了userform,类型为Formgroup
  85. 接下来是添加UserComponentconstructor
    constructor(private fb: FormBuilder, private _userService: UserService) { }
  86. Angular2 的一个很棒之处在于依赖注入。在构造函数中,您可以看到,我们通过 DI 获取了FormBuilderUserService的实例。要阅读更多关于 DI 的信息,请单击此处
  87. 到目前为止,我们的UserComponent应该如下所示

  88. 此时,您可能会遇到错误,因为仍然没有实现ngOnInit事件。让我们继续添加ngOnInit事件,我们将创建并初始化我们的 Reactive User 表单
    ngOnInit(): void {
    
            this.userFrm = this.fb.group({
                Id: [''],
                FirstName: ['', Validators.required],
                LastName: [''],
                Gender: ['']
            });
            
           this.LoadUsers();
        
         }
  89. 我们正在初始化 User 表单,指定表单元素和验证规则。目前,表单初始化为空字符串‘’。
  90. 接下来,让我们创建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);
    
               }
  91. SubscribeObservable的一部分,我们在前面的步骤中讨论过。一旦用户加载完成,它就会将其保存在users变量中。如果发生任何错误,错误消息将保存在msg变量中。indLoading是我们在这里使用的布尔变量,用于在完整响应加载之前显示加载消息。

  92. 接下来,让我们添加三个方法来显示用于添加更新删除用户的模态弹出窗口。为这些函数添加以下代码

    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();
       }
  93. 所有这些方法都相似。让我们以AddUser方法为例并对其进行解释。首先,我们将当前 DB 操作存储在dpops变量中,该变量的类型为DBOperation枚举。接下来,我们调用SetControlsState方法,该方法将启用或禁用表单控件。接下来的变量设置模态弹出窗口的标题和按钮文本。仅在AddUser函数中,我们才重置表单以清除它。接下来,我们调用modal.open()函数来显示模态弹出窗口。在编辑和删除用户方法中,我们以参数形式获取UserID,调用Observable的 filter 方法从用户列表中获取单个用户。filter 语法类似于 C# 中的匿名方法。下一行是将单个用户分配给 user 表单,这将为前端设置值,轻而易举。

  94. 让我们创建SetControlsState,它将启用或禁用表单。Reactive表单具有enabledisable方法,这些方法可以使控件只读和可编辑。

    SetControlsState(isEnable: boolean)
    {
     isEnable ? this.userFrm.enable() : this.userFrm.disable();
    }
  95. 下一个方法是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;
        }
      }
  96. 代码非常简单且自明。一旦我们提交表单,它就会发送所有值,我们可以通过.value属性获取这些值。TypeScript 部分就到此为止。

  97. 让我们为UserComponent编写 HTML 模板。双击user.component.html进行编辑

  98. 将以下代码复制到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">&times;</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>
  99. 如果您查看添加按钮,我们正在使用(click)函数调用AddUser函数,这是我们之前讨论过的event绑定示例。

  100. 接下来,我们使用*ngIf,即structural directives,根据indLoading布尔变量显示加载消息。

  101. 接下来,我们使用*ngFor structural directive 来遍历users数组并显示用户信息。

  102. 下一个代码是用于模态弹出窗口的。您可以看到#modal占位符,我们将在 TypeScript 端使用它通过@ViewChild来访问 open 和 dismiss 函数。

  103. 接下来,我们正在创建表单。(ngSumbit)事件会将表单数据发送到 TypeScriptonSumit函数。

  104. 通过[formgorup]属性绑定,我们正在分配我们已在 TypeScript 端创建的userform。我们通过formControlName属性告诉模板相应的表单控件。

  105. 在表单有效之前,添加编辑按钮将是禁用的。这由[disabled]属性绑定处理,直到userform.invalid属性变为启用。

  106. UserComponent就到此为止了。现在,让我们为UserComponent添加路由,并将其添加到AppModule

  107. 双击app文件夹中的app.routing.ts进行编辑

  108. 通过以下代码导入UserComponent

    import { UserComponent } from './components/user.component';
  109. 如下所示添加UserComponent路由

    { path: 'user', component: UserComponent }

    最终的app.routing.ts应如下所示

  110. 通过双击编辑app.component.ts文件

  111. 将 User Component 添加到 App Module

    import { UserComponent } from './components/user.component';
  112. 在 declaration 部分添加UserComponent,最终的AppModule应如下所示

  113. 添加用户管理的菜单项。双击app.component.ts并添加以下行

    <li><a [routerLink]="['user']">Users Management</a></li>
  114. 最终的app.component.ts应如下所示

  115. 编译并运行应用程序。

历史

  • 2017 年 4 月 16 日:创建
  • 2017 年 5 月 21 日:添加了 第 2 部分
  • 2017 年 8 月 12 日:附加了 Angular 4 解决方案
© . All rights reserved.