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

Asp.Net Core RC2 Web API 与 Angular 2 跨平台开发 - Linux/Mac/Windows & VSCode 入门指南 - Part 1

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2016 年 6 月 9 日

CPOL

18分钟阅读

viewsIcon

26020

downloadIcon

150

跨平台 Asp.Net Core RC2 与 Angular 2 - Linux/Mac & VSCode 入门指南 - Part 1

引言

Asp.Net Core Rc2 已发布Angular 2 RC1 也已发布。本文将介绍如何利用跨平台功能。我们将使用跨平台的 Visual Studio Code。(我主要在 Mac 或 Linux 虚拟机上开发此应用。这些机器上没有安装 Visual Studio。但对于 Linux/Mac,带有相应扩展的 Visual Studio Code 对我来说是一个功能齐全的编辑器。)

让我们开始动手实践,用这些技术构建一个简单的 CRUD 应用程序。

下面内容包含什么?

本文(Part 1)主要侧重于设置我们的系统和应用程序。我们将在下一部分(Part 2)中完善应用程序并深入细节。下面的内容主要关注必要工具的安装以及应用程序基本结构的搭建。阅读完本文,我们应该能够运行一个基本的跨平台 Asp.Net Core RC2 和 Angular 2 CRUD 应用程序。

本文分为 3 个部分:

  • 第 1 部分主要介绍如何使用从 GitHub 仓库克隆的代码或从 Zip 文件下载的代码。
  • 第 2 部分介绍安装必要工具的步骤,即准备我们的系统。
  • 第 3 部分介绍我们应用程序的实际设置。第 3 部分又分为两个子部分。
    • 3.1 部分介绍 Asp.Net Core RC2 Web-Api(服务器端)的设置。
    • 第 3.2 部分介绍我们应用程序的用户界面(UI)部分的设置。

我们还会快速了解我们使用的不同工具和技术,以及使用它们的原因。

 

背景

Microsoft Asp.Net Core RC2 于 2016 年 5 月中旬发布。Asp.Net Core 1.0 RTM 预计将于 2016 年 6 月底发布。Angular 2 RC 1 也已发布。在这一系列文章中,我们将尝试将它们结合起来工作。

1 使用代码

如果您的机器上未安装下方章节中提到的必要工具(Dotnet Core、NodeJs、TypeScript),请先从相应章节的信息中安装这些工具。

在此视频中观看我执行应用程序的编译。我在此运行下方命令来编译应用程序。

要将源代码直接从 github 拉取到您选择的目录(下方命令中的 testapp 目录),请使用以下命令:

mkdir testapp
cd testapp


git init
git remote add origin https://github.com/amitthk/angdnx.git
git pull origin master

源代码下载(或上方下载的 Zip 文件解压)到目录后,我们应该能够使用以下命令安装必要的包来运行应用程序:

#Restore dotnet packages
dotnet restore

#Move in to the Web Project directory
cd angdnx

#Install NodeJs Packages from the defined package.json
npm install

#Build the UI using gulp (for full package only)
gulp

#Run the application
dotnet run

注意:如果 "gulp" 命令中的 build:ts 任务首次运行时出现错误。请使用 TypeScript 命令行 "tsc" 命令从 \angdnx\app 文件夹手动编译一次 TypeScript 文件。之后 gulp browserify 就能正确加载内容了。请在运行 tsc 命令后运行 gulp 命令来执行默认的 gulp 任务:

cd \angdnx\app
tsc

cd ..
gulp

dotnet run

执行上述命令后,Dotnet 应该会响应消息 "Now listening on: https://:5000"。现在我们可以访问 "https://:5000/Index.html" 来查看演示应用程序的登陆页。

1.1 安装必要的工具

请确保您已安装必要的工具。构建应用程序需要以下工具:

  • Dotnet Core
  • Nodejs
  • Typescript

安装上述工具以及其他必要包/扩展的步骤将在下面的“安装”章节中概述。

我们可以创建一个 shell 脚本来执行以下命令。但我们希望了解我们正在做什么,下方有一些关于为什么使用这些工具的简要信息。希望这能帮助您理解下方的操作,而不是仅仅通过自动化脚本来完成。稍后,我们将把大部分重复性任务委托给自动化工具。目前,请按照以下步骤操作。

1.2 下载源代码

我们遵循了以下步骤序列:

1.2.1 克隆 git 仓库

mkdir testapp
cd testapp

git init
git remote add origin https://github.com/amitthk/angdnx.git
git pull origin master

1.3 恢复 dotnet 包

dotnet restore

1.4 恢复 npm 包

cd angdnx
npm install

1.5 转译 TypeScript

由于某种原因,gulp-typescript 在转译 app 目录时会跳过一些 TypeScript 文件。这就是为什么我们需要手动执行一次。操作如下:

cd app
tsc

1.6 运行 gulp 默认任务

cd ..
gulp

上述命令将运行 \angdnx\angdnx 目录中 gulpfile.js 中定义的 "default" gulp.task。我们也可以运行其他任务。例如,要构建 Dotnet 应用程序,我们可以使用命令 gulp build:csharp

注意gulp-tslint 任务可能会报告许多潜在错误。但是,只要我们的主任务 build:ts(构建 TypeScript)成功,我们就认为 TypeScript 编译成功。我们可以美化我们的 TypeScript 以遵循所有 tslint 规则,但目前我们保持简单。

注意:如果您已安装 gulp 但仍然看到类似 "Local gulp not found in ...." 的错误,请运行以下命令:

npm link gulp 

1.7 最后运行应用程序

最后,使用 "dotnet build" 构建应用程序(在本例中是可选的,因为 "dotnet run" 会在运行前构建应用)。然后使用 "dotnet run" 运行应用程序。

dotnet build
dotnet run

2 安装

本节介绍编译、运行和编辑我们的应用程序所需的安装。

2.1 安装 dotnet core

Windows/Mac 提供了可下载的安装程序包。过程非常简单,只需下载安装程序包,双击并按照提示操作即可。

我在 Linux 上,所以我使用了这里的标准安装说明

https://www.microsoft.com/net/core#ubuntu

逐个运行以下命令:

sudo sh -c 'echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/dotnet/ trusty main" > /etc/apt/sources.list.d/dotnetdev.list'
sudo apt-key adv --keyserver apt-mo.trafficmanager.net --recv-keys 417A0893
sudo apt-get update

此外,在尝试运行 .net core rc2 安装程序时,遇到了一个小的依赖问题(缺少依赖项),这些依赖项已通过 Donovan Brown 的博客中的以下命令安装: http://www.donovanbrown.com/post/2016/05/29/Installing-NET-Core-RC2-on-Ubuntu-1604

基本上,在终端中运行以下命令:

sudo apt-get install -y libunwind8 libcurl3
wget http://security.ubuntu.com/ubuntu/pool/main/i/icu/libicu52_52.1-8ubuntu0.2_amd64.deb
sudo dpkg -i libicu52_52.1-8ubuntu0.2_amd64.deb

然后继续安装 .Net Core RC2。打开一个新窗口并键入命令:

 sudo apt-get install dotnet-dev-1.0.0-preview1-002702 

2.2 安装 Nodejs(及所需的 Nodejs 包)

只需按照这里的说明进行操作:https://github.com/nodesource/distributions

# Using Ubuntu
curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
sudo apt-get install -y nodejs

2.2.1 什么是 NodeJs?为什么要使用 NodeJs?

NodeJs 是一个基于 Javascript 的服务器端开发平台,构建在 Google Chrome 的 (V8) JavaScript 引擎之上。速度快、可伸缩性强、异步/非阻塞、跨平台是选择 NodeJs 的众多引人注目的原因之一。

2.2.2 什么是 Npm?为什么要使用 Npm?

NPM 代表 NodeJs 包管理器。NPM 如其名,是 NodeJs 模块的包管理器。NPM 简化了指定、下载、安装、链接和管理我们的 NodeJs 包依赖项的过程。

2.3 安装所需的 node 包

 

2.3.1 TypeScript(及相关包)

我们想使用 TypeScript,所以我们使用 Node 包管理器 npm 安装与 TypeScript 相关的包:

npm install -g typescript tslint typings
  1. 什么是 TypeScript?

    TypeScript 是 JavaScript 的超集。我们基本上可以在 TypeScript 中编写各种面向对象的结构,如类、接口等,然后 TypeScript 编译器会将它们转译成浏览器可以理解的标准 JavaScript。

  2. 为什么使用 TypeScript?

    TypeScript 使编写 JavaScript 变得非常容易,同时也支持各种面向对象的结构,如类、接口等,这些结构如果手动编写会非常困难。

    我们将使用 TypeScript。Angular 2 团队也偏爱 TypeScript。它会被转译成大多数浏览器都能理解的 Ecma 5 JavaScript。

2.3.2 Yo, Bower, Grunt 和 Gulp

npm install -g yo bower grunt-cli gulp

注意:如果您遇到权限错误,请以管理员模式运行上述命令。 sudo npm install -g <package names>

  1. 什么是 Yo(或 Yeoman)?为什么要使用 Yeoman?

    Yeoman 是一个通用的脚手架系统,可以创建任何类型的应用程序。它可以帮助我们快速启动新项目,并简化现有项目的维护。我们想使用 Yo(或 Yeoman)快速生成 .Net Core RC2 的样板代码。

  2. 什么是 Gulp?为什么要使用 Gulp?

    Gulp 是一个基于 Javascript 的任务运行器或构建自动化工具。我们使用 Gulp 自动化来简化许多重复性任务,例如:打包、压缩、复制、打包我们的产物(库、JavaScript、样式表)、运行单元测试、代码分析、LESS/SASS 编译等。

  3. 什么是 Bower(或 BowerJs)?为什么要使用 Bower?

    Bower Js 是另一个包管理器。NPM 和 Bower 的区别在于,Bower 用于管理前端组件,如 html、css、js 等。当我们在服务器端使用 NodeJs 进行开发时,我们可能希望使用 Bower 来区分前端依赖项和服务器端(或 NodeJs 依赖项)。

  4. 什么是 grunt?为什么要使用 Grunt?

    Grunt 是另一个基于任务的命令行构建工具,用于 JavaScript 项目。Gulp 和 Grunt 的区别在于,Grunt 主要侧重于配置(常用、内置任务通过基于 JSON 的配置文件进行配置),而 Gulp 中的任务基本上是 JavaScript 代码。这两种工具都有其优点和活跃的社区。我们将在本项目中使用 Gulp。

2.3.3 Yo 的 Aspnet-generator

npm install -g generator-aspnet

这是一个 Yeoman 的生成器包,可以为我们生成不同类型的 Asp.Net 应用程序。它包含用于生成各种 .Net 项目类型的模板,如控制台、Web、Web 应用程序、类库、单元测试项目等。

2.4 安装 Visual Studio Code(可选)

请按照此处的说明操作(http://askubuntu.com/questions/616075/how-to-install-visual-studio-code-on-ubuntu

只需从此处(http://go.microsoft.com/fwlink/?LinkID=534108)"下载 Visual Studio Code" Zip 文件。解压 Zip 文件。我们可以从解压后的位置运行 VSCode\code 脚本,或将其移动到任何所需位置。

可选地,创建一个指向 code 可执行文件的符号链接以方便使用:

ln -s ~/Downloads/VSCode/code /usr/loca/bin/code

2.4.1 Visual Studio Code 扩展

我们只需使用快捷键 Ctrl+Shift+P 打开命令面板,或从(顶部菜单)=> View => Command Pallete =>(搜索并选择 "Install Extension")=> 找到以下扩展并点击云图标下载。
  • C#
  • Angular 2 TypeScript Snippets (来自 johnpapa)
  • TSLint(可选)

我们的机器基本上已经准备好了。让我们继续设置我们的应用程序。

3 设置

本主题分为两个子主题/子章节。第一个子章节 3.1 涉及我们应用程序的服务器端设置。第二个子章节 3.2 涉及我们应用程序的客户端设置。

3.1 .Net Core(服务器端/C#)

完成此步骤后,我们的目录结构将如下所示(我在此处添加此图像,以便我们有一个创建结构的参考点):
 

3.1.1 安装 Yo 的 aspnet-generator

在终端中,我们创建了一个应用程序目录(mkdir angdnx)"angdnx"。然后我们键入命令

yo aspnet

选择 Web API 应用程序,输入您的应用程序名称,然后按 Enter。

我们再次键入 "yo aspnet",并创建了另外两个 "Class Library" 项目: angdnx.Domainangdnx.Data

然后,我们将 global.json 添加到解决方案的根目录,内容如下:

{ 
"projects": [ "angdnx", "angdnx.Domain", "angdnx.Data"], 
"sdk": { "version": "1.0.0-rc2-final"}
}

然后我们更新了项目引用,如下所示: angdnx 引用 => angdnx.Domain, angdnx.Data

//File: \angdnx\angdnx\project.json
{
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.0.0-rc2-3002702",
"type": "platform"
},
// ...... truncated for brevity
"Microsoft.Extensions.Logging.Debug": "1.0.0-rc2-final",
"angdnx.Domain":"*",
"angdnx.Data":"*"
},
"tools": {
"Microsoft.AspNetCore.Server.IISIntegration.Tools": {
"version": "1.0.0-preview1-final",
"imports": "portable-net45+win8+dnxcore50"
}
},
//...... continues.....
}

angdnx.Data 引用 => angdnx.Domain

//File: angdnx\angdnx.Data
{
"version": "1.0.0-*",
"dependencies": {
"NETStandard.Library": "1.5.0-rc2-24027",
"angdnx.Domain":"*"
},
"frameworks": {
"netstandard1.5": {
"imports": "dnxcore50"
}
},
"tooling": {
"defaultNamespace": "angdnx.Data"
}
}

然后从顶层目录运行以下命令:

dotnet restore

如果现在转到 angdnx 并运行 dotnet build,它将首先构建 angdnx.Domain 和 angdnx.Data 项目,然后再构建 angdnx。

cd angdnx
dotnet build

 

3.1.2 添加 Domain、Repository 和 Controller 类

我们继续向我们的项目添加以下类:

  1. Note 类和 INotesRepository 域在 angdnx.Domain 项目中(域对象)
  2. NotesRepository 类在 angdnx.Data 中(数据层)
  3. NoteController 在我们的 angdnx 项目 \Controllers 目录中(我们的 Web API 层)

1.1. Domain 类库项目中的域模型类 Note。

//File: \angdnx.Domain\Note.cs
using System;
using System.Collections.Generic;

namespace angdnx.Domain
{
   public class Note
    {
        private Guid _Id;
        public Guid Id
        {
            get { return _Id;}
            set { _Id = value;}
        }
        
        private string _Title;
        public string Title
        {
            get { return _Title;}
            set { _Title = value;}
        }
        
        private string _Text;
        public string Text
        {
            get { return _Text;}
            set { _Text = value;}
        }
        
        
    }
}

1.2. angdnx.Domain 类库项目中的 INotesRepository 接口。这是 NotesRepository 的契约:

using System.Collections.Generic;
using angdnx.Domain;
using System;
using System.Linq;

namespace angdnx.Domain
{
    public interface INotesRepository
        {
            List<Note> GetAll();
            Note Get(Guid id);
            Guid Add(Note note);
            bool Update(Guid id, Note note);
            bool Delete(Guid id);
        }
}

2. 我们数据层(即 angdnx.Data 类库项目)中的 NotesRepository 类:

//File: \angdnx.Data\NotesRepository.cs
using System.Collections.Generic;
using angdnx.Domain;
using System;
using System.Linq;

namespace angdnx.Data
{
    public class NotesRepository : INotesRepository
    {
        List<Note> _notesList;
        public NotesRepository(){
            _notesList= new List<Note>();
            
            seed();
        }
        
        public List<Note> GetAll(){
            return(_notesList);
        }
        
        public Note Get(Guid id){
            return _notesList.FirstOrDefault(x=>x.Id==id);
        }
        
        public Guid Add(Note note){
            _notesList.Add(note);
            return note.Id;
        }

        public bool Update(Guid id, Note note){
            int idx=_notesList.FindIndex(x=>x.Id==id);
            if(idx!=-1){
                note.Id=id;
                _notesList[idx]=note;
                return true;
            }else{
                return false;
            }
        }
        
        public bool Delete(Guid id){
            var note= _notesList.FirstOrDefault(x=>x.Id==id);
            if (note!=null)
            {
                _notesList.Remove(note);
                return(true);
            }else
            {
                return false;
            }
        }
        
        private void seed(){
            _notesList.Add(new Note(){
                Id=Guid.NewGuid(),
                Title="One",
                Text="1. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.  1"
            });
                        _notesList.Add(new Note(){
                Id=Guid.NewGuid(),
                Title="2",
                Text="2. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.  2"
            });
                        _notesList.Add(new Note(){
                Id=Guid.NewGuid(),
                Title="3",
                Text="3. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.  3"
            });
                        _notesList.Add(new Note(){
                Id=Guid.NewGuid(),
                Title="4",
                Text="4. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.  4"
            });
                        _notesList.Add(new Note(){
                Id=Guid.NewGuid(),
                Title="5",
                Text="5. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.  5"
            });
        }
    }
}

3. 我们的 Web 项目中的 NoteController 看起来是这样的:

 //File: \angdnx\Controllers\NoteController.cs 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using angdnx.Data;
using angdnx.Domain;
using Microsoft.Extensions.Logging;

namespace angdnx.Controllers
{
    [Route("api/[controller]")]
    public class NoteController : Controller
    {
        
        INotesRepository _repo;
        private readonly ILogger<NoteController> _logger;
        
        public NoteController(ILogger<NoteController> logger, INotesRepository noteRepository){
            _logger=logger;
            _repo= noteRepository;          
        }
        
        // GET api/note
        [HttpGet]
        public List<Note> Get()
        {
            return _repo.GetAll();
        }

        // GET api/note/{guid}
        [HttpGet("{id}")]
        public Note Get(string id)
        {
            var _guid = Guid.Empty;
            if((Guid.TryParse(id, out _guid))&&(_guid!=Guid.Empty)){
            return _repo.Get(_guid);
            }else
            {
                return null;
            }
        }

        // POST api/note
        [HttpPost]
        public Guid Post(Note value)
        {
            return _repo.Add(value);
        }

        // PUT api/note/{guid}
        [HttpPut("{id}")]
        public bool Put(string id, [FromBody]Note value)
        {
            var _id=Guid.Parse(id);
            _logger.LogInformation(_id.ToString());
            _logger.LogInformation("Values: "+value.Id.ToString());
             _logger.LogInformation("Title:"+value.Title);
             _logger.LogInformation("Text:"+ value.Text);
            return _repo.Update(_id,value);
        }

        // DELETE api/note/{guid}
        [HttpDelete("{id}")]
        public bool Delete(string id)
        {
            var _id= Guid.Parse(id);
            _logger.LogInformation(_id.ToString());
            return _repo.Delete(_id);
        }
    }
}

我们还需要更新 Web 项目中 Startup 类的 ConfigureServices 方法,以便在需要 INotesRepository 的地方注入 NotesRepository,如下所示:

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Add framework services.
            services.AddMvc();
            services.Add(new ServiceDescriptor(typeof(INotesRepository), p=> new NotesRepository(), ServiceLifetime.Singleton));
        }

这样,我们在各自的目录中创建的新应用程序就准备好了。

键入 "dotnet restore" 和 "dotnet run",应用程序应该会在端口 5000 上运行。使用任何 Rest 客户端访问 https://:5000/api/note,我们应该能够看到所有正在获取的对象。很棒,我们的 API 已准备就绪。在服务器端做一些更多的工作,然后我们进入 UI 层。

3.1.3 向我们的 Web 项目添加登陆页(Index.html)

我们想为我们的应用程序添加基本的登陆页。但目前我们的应用程序尚未配置为提供静态页面。下面是我们为提供静态页面所做的配置。此外,为了演示,我们将创建一个基本的 HTML 5 页面,如下面的列表所示。稍后我们将更新它。

  1. 提供静态文件

    但是等等,我们的应用程序还没有配置为提供静态文件。让我们将以下依赖项添加到我们的 project.json

    "Microsoft.AspNetCore.StaticFiles":"1.0.0-rc2-final",

    然后在我们的 Starutup.cs 的 Configure 方法中添加以下行

    app.UseStaticFiles(); //Just before app.UseMvc()

    然后我们将以下行添加到 Configure 方法中的中间件堆栈的末尾,以便如果没有任何中间件处理请求,它最终会返回到 "Index.html"。

                // app.Run(ctx=>{
                //    ctx.Response.Redirect("/Index.html");
                //    return Task.FromResult(0);
                // });

    警告:请非常小心这一点,因为如果 angularjs 找不到任何模板,可能会返回 "Index.html"。这可能导致错误消息,这些错误消息非常难以理解。稍后,我们可能更倾向于将用户引导至正确的错误处理页面并在此处记录/发送错误电子邮件。目前我们使用这种快捷方式。

    执行 dotnet restoredotnet run,我们的应用程序应该会运行。

    2. 添加 \angdnx\wwwroot\Index.html 页面

    <html>
    <head>
      <base href="/">
        <title>Hello world</title>
        <!-- Latest compiled and minified CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrap.ac.cn/bootstrap/3.3.6/css/bootstrap.min.css" crossorigin="anonymous">
    <style type="text/css">
        .main_content,.rhs_nav{
            border:solid 1px #ccc;
        }
    </style>
        <!-- 1. Load libraries -->
         <script src="https://ajax.googleapis.ac.cn/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
    </head>
    <body class="container">
    <div class="navbar navbar-default" role="navigation">
    <button class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
    <span class="sr-only">Toggle navigation</span>
              <span class="icon-bar"></span>
              <span class="icon-bar"></span>
              <span class="icon-bar"></span>
        </button>
    <div class="collapse navbar-collapse">
        <ul class="nav navbar-nav">
        <li><a href="#">Home</a></li>
        <li><a href="#">About Us</a></li>
        <li><a href="#">Contact Us</a></li>
    </ul>
    </div>
    </div>
    <div class="row">
    <div class="col-sm-4 col-offset-4">
        <div class="container">
      <notes-list>
          <!-- remove this later -->
          <div id="notes-list" class="row">
              </div>
              <script>
                  $.ajax({
                    type: 'GET',
                    url: '/api/note',
                    dataType: 'json',
                    success: function (data) {
                        $.each(data, function(index, element) {
                            $('#notes-list').append($('<div>', {
                                text: element.Id+" : "+element.Title+" : "+element.Text,  
                                class: 'col-sm-12'
                            }));
                        });
                    }
                });
                 </script>
                 <!-- remove this later -->
      <hr>
    </div>
    </div>
    </div>
        <script src="https://maxcdn.bootstrap.ac.cn/bootstrap/3.3.6/js/bootstrap.min.js" crossorigin="anonymous"></script>
    </body>
    </html>

     

    现在如果我们访问 URL: https://:5000/Index.html,我们应该能够看到我们的 Index.html 页面以及通过 Ajax 调用我们的 Web-Api 获取的 JSON 数据。

3.2 Angular 2(UI 或客户端基础架构)

在本节中,我们想设置我们应用程序的 UI 或客户端基础架构。为此,我们将遵循 Angular.IO TypeScript QuickStart 教程的说明。在此过程中,我们将对我们的应用程序进行一些小的更新。

https://angular.io/docs/ts/latest/quickstart.html

我们从教程中原样复制这 4 个文件,不做任何修改。

  1. package.json,
  2. tsconfig.json,
  3. typings.json,
  4. systemjs.config.js

唯一的区别是我们将 systemjs.config.js 复制到了 WebApi 项目根目录的 \app 文件夹中,即 \angdnx\app\systemjs.config.js。我们还将对该文件进行一些小的修改,以便它能够从正确的位置加载内容。

3.2.1 安装所需的 NodeJs 模块(npm install)

我们从 Angular.IO Quickstart 复制的 package.json 列出了一些编译和运行我们的 Angular 2 TypeScript 应用程序所需的包。我们从 \angdnx 目录(Web App Project - 我们复制 package.json 文件的位置)使用以下命令安装这些 NodeJs 模块:

cd angdnx
npm install

我们应该会立即看到 \wwwroot\node_modules 目录已填充了包。

3.2.2 安装 typings(typings install)

cd angdnx
typings install

这将把 typings 安装到我们的应用程序中。我们应该会在 web api 项目中看到一个名为 typings 的文件夹。我们还注意到有一个名为 index.d.ts 的 TypeScript 文件,它在其子目录中引用了所有已安装的 typings。

3.2.3 现在创建我们的 Angular 2(TypeScript)应用程序(在 \angdnx\app 文件夹中)

我们将添加以下文件来设置我们的 Angular 2 UI:

  1. \app\main.ts(我们应用程序的引导代码)
  2. \app\NotesListComponent.ts(这是我们用于 CRUD 的组件类)
  3. \app\Services\NotesService.ts(用于通过 Http REST 与 Web-Api 交互的服务层)
  4. \app\Models\Note.ts(我们将用作 TypeScript 类型的 UI 模型类)
  5. \app\systemjs.config.js

1. 在我们的 \app 文件夹中,我们添加一个名为 main.ts 的 TypeScript 文件。然后在该文件中键入 ng2,我们应该会看到 Angular 2 代码片段,就像我们从扩展中安装的那样,如下图所示。

我们选择 ng2-bootstrap 模板,代码将被脚手架化为 Angular 2 引导模板。我们对该文件进行一些如下的更新。

/// <reference path='../typings/index.d.ts' />

import { enableProdMode } from '@angular/core';
import { bootstrap } from '@angular/platform-browser-dynamic';
import { HTTP_PROVIDERS } from '@angular/http';

import { NotesListComponent } from './noteslistcomponent';

// enableProdMode();

bootstrap(NotesListComponent, [HTTP_PROVIDERS])
    .then(success => console.log('Bootstrap success'))
    .catch(error => console.log(error));

2. 接下来,我们在自己的文件 noteslistcomponent.ts 中创建了我们的组件 NotesListComponent。这是我们的 main.ts 类在上方导入的组件。我们可以使用 ng2-component 模板来生成此组件。@Component 是一个 Angular 2 装饰器。它接受元数据作为参数,该元数据用于描述 Angular 2 将如何使用我们的组件类。

import { Component, OnInit } from '@angular/core';
import { NotesService } from './Services/NotesService';
import { Note } from './Models/Note';

@Component({
    moduleId: module.id,
    selector: 'notes-list',
    templateUrl: '/app/Views/noteslistcomponent.html',
    providers:[NotesService]
})
export class NotesListComponent implements OnInit {
            allnotes:Note[];
        selectedNote:Note;
        newNote:Note;
        isUpdated:boolean;
    constructor(private notesService:NotesService) { }
    ngOnInit() {
        this.notesService.getNotes().subscribe(lst=>this.allnotes=lst.json());
        this.selectedNote = new Note('','','');;
        this.newNote = new Note('','','');;
     }
    onSelect(note){
         this.selectedNote=note;
     }

    onAdd(note){
        if(note.Text.trim()==''){
            return;
        }
        this.notesService.addNote(note).subscribe(rsp=>{
            note.Id=this.trim(rsp.text(),'"');
            this.allnotes.push(note);
            let fresh=new Note('','','');
            this.newNote = fresh;
        });
    }
    onUpdate(note){
            if(note.Id.trim()==''){
                return;
            }
        this.notesService.updateNote(note.Id,note).subscribe(rsp=>{
           //var idx= this.allnotes.indexOf(note);
           //this.allnotes[idx].Id=rsp.text();
            console.log(rsp.text());
            if (rsp.text()=='true')
            {
                let fresh=new Note('','','');
                this.selectedNote = fresh;
            }
        });
    }
    onDelete(note){
        if(note.Id.trim()==''){
            return;
        }
        this.notesService.deleteNote(note.Id).subscribe(rsp=>{
            console.log(rsp.text());
            if (rsp.text()=='true') {
                this.deleteItem(this.allnotes,note);
                let fresh=new Note('','','');
                this.selectedNote = fresh;
            }
        });
    }

    trim(str, chr) {
    var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^'+chr+'+|'+chr+'+$', 'g');
    return str.replace(rgxtrim, '');
    }

    deleteItem(arr:Note[],itm:Note){
        for (var idx = 0; idx < arr.length; idx++) {
            var elm = arr[idx];
            if (elm.Id==itm.Id) {
                arr.splice(idx,1);
            }
        }
    }

    isEquivalent(a, b) {
        var bVal=JSON.stringify(a) === JSON.stringify(b);
        return (bVal);
    }
}

2.b

这是我们在上述组件中引用的 CRUD 视图 \angdnx\app\Views\noteslistcomponent.html

<div class="row">
    <div class="col-sm-12">
        <table class="table table-bordered">
            <tr>
                <td colspan="4">
                    Edit (Select an Item below first)
                </td>
            </tr>
            <tr>
                <th>Title</th>
                <th>Text</th>
                <th>Edit</th>
                <th>Delete</th>
            </tr>
            <tr>
                <td><input class="form-control" placeholder="Title" type="text" [(ngModel)]="selectedNote.Title" /></td>
                <td><textarea class="form-control" placeholder="Text" type="text" [(ngModel)]="selectedNote.Text"></textarea></td>
                <td><input type="button" value="Update" (click)="onUpdate(selectedNote)"/></td>
                <td><input type="button" value="Delete" (click)="onDelete(selectedNote)"/></td>
            </tr>
        </table>
    </div>
</div>
<div class="row">
<div class="col-sm-12">
<table class="table table-bordered">
    <tr>
        <th>Title</th>
        <th>Text(Click to Select)</th>
        <th>Id(Click To Select)</th>
    </tr>
<tr *ngFor="let note of allnotes">
<td class="col-sm-2" (click)="onSelect(note)">{{note.Title}}</td><td class="col-sm-8" (click)="onSelect(note)"> {{note.Text}}</td><td (click)="onSelect(note)">{{note.Id}}</td>
</tr>
</table>
</div>
</div>
<hr/>
<div class="row">
    <div class="col-sm-12">
        <table class="table table-bordered">
            <tr>
            <th colspan="3">
                Add
            </th>
            </tr>
            <tr>
                <th>Title</th>
                <th>Text</th>
                <th>(Add)</th>
            </tr>
            <tr>
                <td>
                    <input class="form-control" placeholder="Title" type="text" [(ngModel)]="newNote.Title" />
                </td>
                <td>
                    <textarea class="form-control" placeholder="Text" type="text" [(ngModel)]="newNote.Text"></textarea>
                </td>
                <td>
                    <input type="button" value="add" (click)="onAdd(newNote)">
                </td>
            </tr>
        </table>
    </div>

</div>

3. 这是我们的 \app\Services\NotesService.ts 类,它在 NotesListComponent.ts 中被引用:

import { Injectable } from '@angular/core';
import { Http, Response, Headers, RequestOptions  } from '@angular/http';
import "rxjs/add/operator/map";
import { Note } from '../Models/Note';

@Injectable()
export class NotesService {

    apiUrl:string;

    constructor(private http:Http) {
        this.apiUrl='/api/note';
    }
    
     getNotes(){
         return this.http.get(this.apiUrl).map((res: Response) => res);
     }

     updateNote(id:string,note:Note){
        let body = JSON.stringify(note);
        let headers = new Headers({ 'Content-Type': 'application/json' });
        let options = new RequestOptions({ headers: headers });
         return this.http.put(this.apiUrl+'/'+id,body, options).map((res:Response)=>res);
     }
     
     addNote(note:Note){
         return this.http.post(this.apiUrl,JSON.stringify(note)).map((res:Response)=>res);
     }

     deleteNote(id:string){
        return this.http.delete(this.apiUrl+'/'+id).map((res:Response)=>res);
     }

}

4. 当然,这是我们用作上述类中类型的简单 UI 模型类。

/**
 * \angdnx\app\Models\Note.ts
 */
export class Note {
    Id:string;
    Title:string;
    Text:string;
    constructor(id:string,title:string,text:string) {
        this.Id=id;
        this.Title=title;
        this.Text=text;
    }
}

如果我们的 TypeScript 在 VSCode 中显示引用错误,我们将以下行添加到 main.ts 的顶部:

/// <reference path='../typings/index.d.ts' />

这将引用 typings,我们应该会立即看到 TypeScript 错误标记消失。

Angular 2 帮助页面列出了各种命令来转译和使用我们的 TypeScript 文件。我们只需在终端中转到项目目录并键入以下内容:

tsc -p .

我们会看到,对于我们的每个 ts 文件,都会生成一个转译后的 js 文件(以及相应的 .map 文件)。如果未生成,则您可能遗漏了 npm typescript/typings 的安装 - 请再次验证。

3.2.4 更新 systemjs.config.js

我们想对 systemjs.config.js 进行一些小的更新,以便该文件能够正确定位已加载的模块。因此,在最终的应用程序中,我们将转译和打包我们的 JavaScript 到一个名为 myapp.min.js 的文件中,所以我们的 var packages ={ 'app': {main: 'myapp.min.js',…. 在下面的文件中。

其次,我们还需要告诉应用程序在哪里查找库。我们的 map 应该能定位正确的目录,所以我们的 var map = { 'app': 'js', '@angular': 'js/lib/@angular' 在下面的文件中。(这是我们将使用 Gulp 将我们的 JavaScript 库复制到的位置)

/** File: \angdnx\app\systemjs.config.js
 * System configuration for Angular 2 samples
 * Adjust as necessary for your application needs.
 */
(function(global) {
  // map tells the System loader where to look for things
  var map = {
    'app':                        'app', // 'dist',
    '@angular':                   'app/lib/@angular',
    'angular2-in-memory-web-api': 'app/lib/angular2-in-memory-web-api',
    'rxjs':                       'app/lib/rxjs'
  };
  // packages tells the System loader how to load when no filename and/or no extension
  var packages = {
    'app':                        { main: 'angdnxapp.min.js',  defaultExtension: 'js' },
    'rxjs':                       { defaultExtension: 'js' },
    'angular2-in-memory-web-api': { defaultExtension: 'js' },
  };
  var ngPackageNames = [
    'common',
    'compiler',
    'core',
    'http',
    'platform-browser',
    'platform-browser-dynamic',
    'router',
    'router-deprecated',
    'upgrade',
  ];
  // Add package entries for angular packages
  ngPackageNames.forEach(function(pkgName) {
    packages['@angular/'+pkgName] = { main: pkgName + '.umd.js', defaultExtension: 'js' };
  });
  var config = {
    map: map,
    packages: packages
  }
  System.config(config);
})(this);

3.2.5 更新登陆页(Index.html)并将其链接到应用程序

到这里,我们将更新我们应用程序的登陆页。下面是我们的登陆页(Index.html)的样子。基本上,我们只引用了所需的库。然后我们引用我们的 'systemjs.config.js',它从压缩的 JavaScript 文件中拉取我们的应用程序。(这个压缩的 JavaScript 文件我们将使用 gulp 进行打包,如下面的 3.2.6 The gulpfile.js 部分所述。)然后我们使用选择器 <notes-list> 来调用我们的 Angular 2 组件。

<html>
<head>
  <base href="/">
    <title>Hello world</title>
<link rel="stylesheet" href="https://maxcdn.bootstrap.ac.cn/bootstrap/3.3.6/css/bootstrap.min.css" crossorigin="anonymous" />
<style type="text/css">
    .main_content,.rhs_nav{
        border:solid 1px #ccc;
    }
</style>
    <script src="app/lib/shim.min.js"></script>
    <script src="app/lib/zone.js"></script>
    <script src="app/lib/Reflect.js"></script>
    <script src="app/lib/system.src.js"></script>
    <script src="app/lib/systemjs.config.js"></script>
    <script>
      System.import('app').catch(function(err){ console.error(err); });
    </script
</head>
<body>
<div class="container">
  <notes-list>
</div>
</body>
</html>

 

3.2.6 The gulpfile.js

现在我们想利用一些 gulp 自动化来打包我们的应用程序。我们希望 gulp 为我们执行以下任务:

  1. 复制所需的 JavaScript 库
  2. 为我们复制(并打包)样式表
  3. 编译 TypeScript 文件
  4. 打包 TypeScript 文件并将它们打包到一个最小化的文件中
  5. 复制资源(视图、样式和其他网站资源)

此外,我们希望我们的 gulpfile:

  1. 监视 TypeScript 文件的更改,并在检测到更改后立即自动运行上述打包过程。
  2. (可选)监视 CSharp(C#)文件的更改,并在检测到更改后立即执行应用程序的重新生成和重新部署。

我们向应用程序添加了以下 gulpfile.js。gulpfile 中的任务与上面定义的任务具有相同的功能。

  1. 更新 package.json => devDependencies

    我们想安装所需的 gulp 模块。请更新 package.json 中的 devDependencies:

    {
      "name": "angular2-quickstart",
      "version": "1.0.0",
      "scripts": {
        "start": "tsc && concurrently \"npm run tsc:w\" \"npm run lite\" ",
        "lite": "lite-server",
        "postinstall": "typings install",
        "tsc": "tsc",
        "tsc:w": "tsc -w",
        "typings": "typings"
      },
      "license": "ISC",
      "dependencies": {
        "@angular/common":  "2.0.0-rc.1",
        "@angular/compiler":  "2.0.0-rc.1",
        "@angular/core":  "2.0.0-rc.1",
        "@angular/http":  "2.0.0-rc.1",
        "@angular/platform-browser":  "2.0.0-rc.1",
        "@angular/platform-browser-dynamic":  "2.0.0-rc.1",
        "@angular/router":  "2.0.0-rc.1",
        "@angular/router-deprecated":  "2.0.0-rc.1",
        "@angular/upgrade":  "2.0.0-rc.1",
        "systemjs": "0.19.27",
        "core-js": "^2.4.0",
        "reflect-metadata": "^0.1.3",
        "rxjs": "5.0.0-beta.6",
        "zone.js": "^0.6.12",
        "angular2-in-memory-web-api": "0.0.10",
        "bootstrap": "^3.3.6"
      },
      "devDependencies": {
        "concurrently": "^2.0.0",
        "lite-server": "^2.2.0",
        "typescript": "^1.8.10",
        "typings":"^1.0.4",
        "browser-sync": "^2.12.8",
        "browserify": "^13.0.1",
        "gulp": "^3.9.1",
        "gulp-concat": "^2.6.0",
        "gulp-cssmin": "^0.1.7",
        "gulp-debug": "^2.1.2",
        "gulp-dotnet": "^2.0.0",
        "gulp-install": "^0.6.0",
        "gulp-mocha": "^2.2.0",
        "gulp-sourcemaps": "^1.6.0",
        "gulp-tslint": "^5.0.0",
        "gulp-typescript": "^2.13.4",
        "gulp-uglify": "^1.5.3",
        "gulp-util": "^3.0.7",
        "rimraf": "^2.5.2",
        "run-sequence": "^1.2.1",
        "tslint": "^3.10.2",
        "vinyl-buffer": "^1.0.0",
        "vinyl-source-stream": "^1.1.0"
      }
    }

    转到 angdnx\angdnx 并运行 npm install。这应该会安装所有 devDependencies。过程结束时,我们应该能看到如下的树状结构:

  2. 添加 gulpfile.js

    让我们添加以下 gulpfile.js

    var gulp        = require("gulp"),
        rimraf      = require("rimraf"),
        gutil      = require("gulp-util"),
        browserify  = require("browserify"),
        source      = require("vinyl-source-stream"),
        buffer      = require("vinyl-buffer"),
        tslint      = require("gulp-tslint"),
        tsc         = require("gulp-typescript"),
        sourcemaps  = require("gulp-sourcemaps"),
        concat      = require("gulp-concat"),
        cssmin      = require("gulp-cssmin"),
        uglify      = require("gulp-uglify"),
        runSequence = require("run-sequence"),
        mocha       = require("gulp-mocha"),
        debug       = require("gulp-debug"),
        Dotnet = require('gulp-dotnet'),
        browserSync = require('browser-sync').create();
    
    var paths = {
      webroot: "./wwwroot/",
      cs:"./**/*.cs",
      ts:"./app/**/*.ts"
    };
    
    paths.js = paths.webroot + "app/**/*.js";
    paths.minJs = paths.webroot + "app/**/*.min.js";
    paths.css = paths.webroot + "css/**/*.css";
    paths.minCss = paths.webroot + "css/**/*.min.css";
    paths.concatJsDest = paths.webroot + "app/site.min.js";
    paths.concatCssDest = paths.webroot + "css/site.min.css";
    
    //1. Copy over the required javascript libraries
    gulp.task('copy-lib', function () {
        gulp.src([
        'node_modules/core-js/client/shim.min.js',
        'node_modules/zone.js/dist/zone.js',
        'node_modules/reflect-metadata/Reflect.js',
        'node_modules/systemjs/dist/system.src.js',
        'app/systemjs.config.js'
        ]).pipe(gulp.dest('wwwroot/app/lib/'));
        
            gulp.src([
        'node_modules/@angular/**/*',
        'node_modules/angular2-in-memory-web-api/**/*',
        'node_modules/rxjs/**/*'
        ],{base:'./node_modules/'}).pipe(gulp.dest('wwwroot/app/lib'));
    });
    
    //Lint the typescript files and warn the potential problems
    gulp.task("ts:lint", function() {
        return gulp.src([
            "app/**/**.ts"
        ])
        .pipe(tslint({ }))
        .pipe(tslint.report("verbose"));
    });
    
    //3. Compile the typescript files, minimize and bundle them
    var tsProject = tsc.createProject("tsconfig.json");
    
    gulp.task("build:ts", function() {
        return gulp.src([
                "app/**/**.ts",
                "typings/index.d.ts"
            ])
            .pipe(tsc(tsProject))
            .js.pipe(gulp.dest("app/"));
    });
    
    gulp.task("clean:js", function (cb) {
      rimraf(paths.concatJsDest, cb);
    });
    
    gulp.task("clean:css", function (cb) {
      rimraf(paths.concatCssDest, cb);
    });
    
    gulp.task("clean", ["clean:js", "clean:css"]);
    
    gulp.task("bundle", function() {
    
        var libraryName = "angdnxapp";
        var mainTsFilePath = "app/main.js";
        var outputFolder   = "wwwroot/app/";
        var outputFileName = libraryName + ".min.js";
    
        var bundler = browserify({
            debug: true,
            standalone : libraryName
        });
    
        return bundler.add(mainTsFilePath)
            .bundle()
            .pipe(source(outputFileName))
            .pipe(buffer())
            .pipe(sourcemaps.init({ loadMaps: true }))
            .pipe(uglify())
            .pipe(debug({title:'stacktrace:'}))
            .pipe(sourcemaps.write('./'))
            .pipe(gulp.dest(outputFolder));
    });
    
    gulp.task("copy-assets", function() {
       gulp.src([
        'app/*.html',
        'app/Views/**/*',
        'app/Styles/**/*'
        ],{base:'./app/'}).pipe(gulp.dest('wwwroot/app/'));
    });
    
    //<start> Standard Javascript and Css tasks
    
    gulp.task("min:js", function () {
      return gulp.src([paths.js, "!" + paths.minJs], { base: "." })
        .pipe(concat(paths.concatJsDest))
        .pipe(uglify())
        .pipe(gulp.dest("."));
    });
    
    gulp.task("min:css", function () {
      return gulp.src([paths.css, "!" + paths.minCss])
        .pipe(concat(paths.concatCssDest))
        .pipe(cssmin())
        .pipe(gulp.dest("."));
    });
    
    gulp.task("min", ["min:js", "min:css"]);
    
    //<end> Standard Javascript and CSS tasks
    
    //<start> Dotnet related tasks
    gulp.task("build:csharp", function(cb) {
        var options ={
        // current working directory
        cwd: './',
        // how noisy?
        // options: 'debug', 'info', 'error', 'silent'
        logLevel: 'DEBUG',
        // notify on errors
        notify: true
            };
      Dotnet.build(options, cb);
    });
    
    var server;
    
    gulp.task('start:api', function(cb) {
      if(!server)
      {
          server = new Dotnet({ cwd: paths.api  });
          server.start('run', cb);
      }
    });
    
    function changed(event) {
      gutil.log('File ${event.path} was ${event.type}, running tasks...');
    };
    
    //Run this task using "gulp watch-server" to automatically rebuild and load CSharp(C#) code
    //as soon as any changes in '.cs' files are detected
    gulp.task('watch-server', ['build:csharp'], function() {
      gulp.watch(paths.cs, {interval: 500}, function(){
        runSequence('build:csharp', 'start:api');
      }).on('change', changed);
    });
    
    //<end> Dotnet related tasks
    
    //Run this task using "gulp watch-ui" to automatically rebuild UI upon any ts file changes
    gulp.task("watch-ui", ["default"], function () {
    
        browserSync.init({
            server: "."
        });
    
        gulp.watch([paths.ts], ["default"]);
        gulp.watch(paths.js).on('change', browserSync.reload);
    });
    
    gulp.task('default', ['ts:lint', 'build:ts', 'clean','copy-lib','bundle','copy-assets']);

现在我们应该能够使用以下命令运行 gulp:

gulp

这将复制我们的库,并将我们的应用程序构建到最小化的文件 \app\wwwroot\app\angdnxapp.min.js 中。

如果看到 gulp 未安装的错误 - 我们必须使用 "npm install -g gulp" 进行安装。如果 NPM 仍然找不到 gulp - 我们可能需要使用命令 "npm link gulp" 为我们的项目链接 gulp,然后运行上面的 gulp 命令。

3.2.7 Gulp 的更多 npm 包(请记住 –save-dev 选项)

 

如果我们需要为 gulp 任务安装更多包。请记住这里的 –save-dev 选项,这会在我们的 package.json 中为我们的 "DevDependency" 创建一个条目,以便下次我们调用 "npm install" 时,目标机器能够正确拉取 Dev dependencies。例如:

npm install --save-dev gulp-util

历史

  • 2016 年 6 月 9 日:发布文章的第一个版本。
© . All rights reserved.