目录
在Visual Studio 2015更新3中创建MasterChef2应用程序
将Bootstrap样式应用于Angular 2 Front UI
介绍
在Master Chef Part 1和Master Chef Part 2中,我介绍了如何将ASP.NET Core(框架)与Angular JS和Fluent NHibernate集成。在本文中,我想创建一个ASP.NET Core(Net Core)应用程序。您应该知道NHibernate还没有Net Core版本,因此我切换到Entity Framework Core。2016年9月14日,Angular 2 Final发布了。Angular 2是AngularJS的第二个主要部分,完全用TypeScript编写。对于使用Angular 1.x的开发人员,您可能会发现Angular 2发生了很大的变化,因为它完全基于组件并且面向对象,并且通过增强的DI能力变得容易得多。我将向您展示如何在Visual Studio 2015 Update 3中构建Angular2应用程序。
在Visual Studio 2015更新3中创建MasterChef2应用程序
从https://marketplace.visualstudio.com/items?itemName=JacquesEloff.MicrosoftASPNETandWebTools-9689下载并安装最新的ASP.NET Web工具。然后从https://www.microsoft.com/zh-cn/download/details.aspx?id=48593安装TypeScript 2 。
在Visual C#/Web中,选择ASP.NET Core Web应用程序(.NET Core)。
ASP.NET Core具有两种应用程序。
- ASP.NET Core .NET Framework应用程序是使用.NET Framework在Windows上运行的应用程序。
- ASP.NET Core .NET Core应用程序是使用.NET Core在Windows,Linux和OS X上运行的跨平台应用程序。
这次我们选择Web应用程序.NET core。
选择“空”模板,然后取消选中“云中的主机”。
看一下ASP.NET Core Web解决方案的结构。它创建“src”文件夹,实际项目在“src”文件夹下。在此src文件夹中,有一个特殊文件夹——wwwroot,该文件夹将容纳我们所有的实时Web文件。
更新project.json(Nuget程序包)
{
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.0.1",
"type": "platform"
},
"Microsoft.AspNetCore.Diagnostics": "1.0.0",
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.1",
"Microsoft.Extensions.Logging.Console": "1.0.0",
"Microsoft.AspNetCore.Mvc": "1.0.1",
"Microsoft.AspNetCore.StaticFiles": "1.0.0",
"Microsoft.EntityFrameworkCore.SqlServer" : "1.1.0",
"Microsoft.EntityFrameworkCore.Tools": {
"version": "1.1.0-preview4-final",
"type": "build"
},
"Microsoft.EntityFrameworkCore.Design": "1.1.0",
"Microsoft.EntityFrameworkCore.SqlServer.Design": "1.1.0",
"Newtonsoft.Json": "9.0.1"
},
"tools": {
"Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final",
"Microsoft.EntityFrameworkCore.Tools": "1.1.0-preview4-final"
},
"frameworks": {
"netcoreapp1.0": {
"imports": [
"dotnet5.6",
"portable-net45+win8"
]
}
},
"buildOptions": {
"emitEntryPoint": true,
"preserveCompilationContext": true
},
"runtimeOptions": {
"configProperties": {
"System.GC.Server": true
}
},
"publishOptions": {
"include": [
"wwwroot",
"web.config"
]
},
"scripts": {
"postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
}
}
新增了什么?我想补充Asp.NetCore.MVC,Asp.NetCore.StaticFiles,EntityFrameworkCore和NewtonSoft.Json。
保存project.json后,Visual Studio将自动还原引用。
配置MVC和静态文件
转到Startup.cs。
在ConfigureServices(…)中添加services.AddMvc(),在Configure(…)中添加app.UseMvc。
添加app.UseDefaultFiles()和app.UseStaticFiles(),使wwwroot下的index.html 直接服务于客户端。
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
app.UseDefaultFiles();
app.UseStaticFiles();
}
从现有数据库创建模型
在前面的文章中,我们已经创建了MasterChef数据库。现在我们需要从现有数据库创建模型类。为了使反向工程,从现有的数据库中,我们需要安装Microsoft.EntityFrameworkCore.Tools,Microsoft.EntityFrameworkCore.Design和Microsoft.EntityFrameworkCore.SqlServer.Design。
工具–> NuGet软件包管理器–>软件包管理器控制台
运行以下命令以从现有数据库创建模型。如果收到错误消息,指出术语'Scaffold-DbContext'未识别为cmdlet的名称,请关闭并重新打开Visual Studio。
Scaffold-DbContext“Server=.\sqlexpress;Database=MasterChef;Trusted_Connection=True;” Microsoft.EntityFrameworkCore.SqlServer——OutputDir模型
运行此命令后,你可以在模型文件夹中看到MasterChefContext.cs,Recipes.cs,RecipeSteps.cs和RecipeItems.cs已被创建。我对带有复数“s”的Model类名称不满意。
所以我改名Recipes.cs为Recipe.cs,RecipeSteps.cs为RecipeStep.cs, RecipeItems.cs为RecipeItem.cs。
然后创建存储库类。
我们使用存储库模式来分离检索数据并将其从作用在模型上的业务逻辑映射到实体模型的逻辑。业务逻辑应该与构成数据源层的数据类型无关。
存储库在应用程序的数据源层和业务层之间进行中介。它查询数据源中的数据,将数据从数据源映射到业务实体,并将业务实体中的更改持久保存到数据源。存储库将业务逻辑与与基础数据源的交互分开。
当获得配方对象列表时,我们还需要加载配方步骤和配方项目。我们如何在Entity Framework Core中做到这一点?
Entity Framework Core允许您使用模型中的导航属性来加载相关实体。有三种常见的ORM模式用于加载相关数据。
- 立即加载意味着相关数据作为初始查询的一部分从数据库加载。
- 显式加载意味着稍后将显式地从数据库加载相关数据。
- 延迟加载意味着在访问导航属性时,相关数据将透明地从数据库加载。在EF Core中,延迟加载是不可能的。
在这里,我们使用Include方法来指定要包含在查询结果中的相关数据。
public IList<recipe> GetAllRecipes()
{
try
{
var recipes = _dbContext.Recipes.Include(x => x.RecipeSteps).ThenInclude(y => y.RecipeItems).ToList();
recipes.ForEach(x => x.RecipeSteps = x.RecipeSteps.OrderBy(y => y.StepNo).ToList());
return recipes;
}
catch (Exception ex)
{
throw ex;
}
}
添加Web API控制器
创建“api”文件夹,然后右键单击api文件夹以添加新项目。在ASP.NET中,选择“Web API控制器类”模板。我们将类命名为RecipesController.cs
目前,在RecipesController类上,我们仅在此处设置GET请求以请求所有配方。
我们添加_repository成员来处理数据库内容。
[Route("api/[controller]")]
public class RecipesController : Controller
{
Repository _repository = Repository.Instance;
// GET: api/recipes
[HttpGet]
public IEnumerable<recipe> Get()
{
return _repository.GetAllRecipes();
}
}
现在,从浏览器中,我们测试一下Web API是否正常工作。
从IIS Express运行,并获得以下结果。
看起来像是工作了。但是,如果您从数据库中运行查询,则会发现结果不太正确。看起来只有第一个配方、配方步骤和配方项目。发生了什么?让我们调试一下。我把断点放在Repository类上。快速查看查询结果。
实体框架核心正常运行。然后问题在JSON序列化期间发生。最后,我找到了原因。引用循环会导致JSON序列化无法正常工作。
看看我们的模型类。食谱有引用集合RecipeSteps,而RecipeStep有引用项目Recipe。它们互相引用导致引用循环。同样,RecipeStep具有引用集合RecipeItems,而RecipeItem具有引用项RecipeStep。它们也会引起引用循环。
解决方案是让JSON忽略RecipeStep中的Recipe和RecipeItem中的RecipeStep。这样,引用循环就消失了,序列化将正确完成。
RecipeStep 类。
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace MasterChef2WebApp.Models
{
public partial class RecipeStep
{
public RecipeStep()
{
RecipeItems = new HashSet<recipeitem>();
}
public Guid RecipeStepId { get; set; }
public Guid RecipeId { get; set; }
public int StepNo { get; set; }
public string Instructions { get; set; }
public virtual ICollection<recipeitem> RecipeItems { get; set; }
[JsonIgnore]
public virtual Recipe Recipe { get; set; }
}
}
RecipeItem 类。
using System;
using Newtonsoft.Json;
namespace MasterChef2WebApp.Models
{
public partial class RecipeItem
{
public Guid ItemId { get; set; }
public Guid RecipeStepId { get; set; }
public string Name { get; set; }
public decimal Quantity { get; set; }
public string MeasurementUnit { get; set; }
[JsonIgnore]
public virtual RecipeStep RecipeStep { get; set; }
}
}
现在,我们再次运行它以检查Web API,这次一切正常了。
TypeScript
TypeScript是由Microsoft开发和维护的一种免费和开放源代码编程语言。它是JavaScript的严格超集,并向该语言添加了可选的静态类型和基于类的面向对象编程。对于大型的客户端项目,TypeScript将使我们能够生成更健壮的代码,并且这些代码也可以完全部署在运行普通JavaScript文件的任何位置。由于TypeScript是JavaScript的超集,因此可以与任何JavaScript代码一起使用而不会出现问题。在Visual Studio中使用TypeScript还可以增强智能感。
添加tsconfig.json
首先,我们需要添加一个类型脚本配置文件。只需右键单击您的项目以添加新项目。在客户端中,选择TypeScript JSON配置文件。
用以下内容替换默认设置。
{
"compileOnSave": false,
"compilerOptions": {
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"module": "system",
"moduleResolution": "node",
"noImplicitAny": false,
"noEmitOnError": false,
"removeComments": false,
"sourceMap": true,
"target": "es5"
},
"exclude": [
"node_modules",
"wwwroot"
]
}
该compileOnSave信号指示IDE 在保存时为给定的tsconfig.json生成所有文件。compilerOptions配置将影响智能感知以及我们的外部TypeScript编译器的工作方式。通过排除配置中的文件夹,我们告诉Visual Studio 2015提供的内置TypeScript编译器禁用该位置内的外部TypeScript文件的编译。请注意,我们将使用NPM下载包括Angular2在内的TypeScript包。所有这些软件包都将在node_modules文件夹下。我们不希望Visual Studio编译它们,这就是为什么我们排除node_modules文件夹的原因。
NPM
NPM最初是为管理开源NodeJS框架的软件包而创建的。package.json是管理项目的NPM包文件。
右键单击项目,然后选择添加>新建项目。在对话框中,选择“NPM配置文件”。
通过添加以下依赖项来修改“package.json”文件。
{
"version": "1.0.0",
"name": "asp.net",
"dependencies": {
"@angular/common": "2.0.0",
"@angular/compiler": "2.0.0",
"@angular/core": "2.0.0",
"@angular/forms": "2.0.0",
"@angular/http": "2.0.0",
"@angular/platform-browser": "2.0.0",
"@angular/platform-browser-dynamic": "2.0.0",
"@angular/router": "3.0.0",
"@angular/upgrade": "2.0.0",
"core-js": "^2.4.1",
"reflect-metadata": "^0.1.8",
"rxjs": "5.0.0-rc.4",
"systemjs": "^0.19.41",
"typings": "^1.3.2",
"zone.js": "^0.7.2",
"moment": "^2.17.0"
},
"devDependencies": {
"gulp": "^3.9.1",
"gulp-clean": "^0.3.2",
"gulp-concat": "^2.6.1",
"gulp-less": "^3.3.0",
"gulp-sourcemaps": "^1.9.1",
"gulp-typescript": "^3.1.3",
"gulp-uglify": "^2.0.0",
"typescript": "^2.0.10"
},
"scripts": {
"postinstall": "typings install dt~core-js --global"
}
}
保存“package.json”后,Visual Studio将自动还原软件包。所有软件包都安装在node_modules文件夹下。带有@符号的软件包是新Angular 2捆绑包的一部分:其他软件包正在加载库,帮助工具。
Gulp
就像Grunt一样,Gulp是JavaScript任务执行者。但是,Gulp更偏向代码而不是配置。由于您的任务是用代码编写的,因此Gulp感觉更像是一个构建框架,为您提供了创建满足您特定需求工具的工具。我们将使用Gulp作为JavaScript Task Runner来自动化我们的客户端脚本。
为我们的Gulp配置添加一个新文件。右键单击项目解决方案,然后选择添加>新建项目。在客户端模板下,选择“Gulp配置文件”
然后,使用以下代码替换默认生成的配置:
var gulp = require('gulp'),
gp_clean = require('gulp-clean'),
gp_concat = require('gulp-concat'),
gp_less = require('gulp-less'),
gp_sourcemaps = require('gulp-sourcemaps'),
gp_typescript = require('gulp-typescript'),
gp_uglify = require('gulp-uglify');
/// Define paths
var srcPaths = {
app: ['Scripts/app/main.ts', 'Scripts/app/**/*.ts'],
js: [
'Scripts/js/**/*.js',
'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',
'node_modules/typescript/lib/typescript.js',
'node_modules/ng2-bootstrap/bundles/ng2-bootstrap.min.js',
'node_modules/moment/moment.js'
],
js_angular: [
'node_modules/@angular/**'
],
js_rxjs: [
'node_modules/rxjs/**'
]
};
var destPaths = {
app: 'wwwroot/app/',
js: 'wwwroot/js/',
js_angular: 'wwwroot/js/@angular/',
js_rxjs: 'wwwroot/js/rxjs/'
};
// Compile, minify and create sourcemaps all TypeScript files
// and place them to wwwroot/app, together with their js.map files.
gulp.task('app', ['app_clean'], function () {
return gulp.src(srcPaths.app)
.pipe(gp_sourcemaps.init())
.pipe(gp_typescript(require('./tsconfig.json').compilerOptions))
.pipe(gp_uglify({ mangle: false }))
.pipe(gp_sourcemaps.write('/'))
.pipe(gulp.dest(destPaths.app));
});
// Delete wwwroot/app contents
gulp.task('app_clean', function () {
return gulp.src(destPaths.app + "*", { read: false })
.pipe(gp_clean({ force: true }));
});
// Copy all JS files from external libraries to wwwroot/js
gulp.task('js', function () {
gulp.src(srcPaths.js_angular)
.pipe(gulp.dest(destPaths.js_angular));
gulp.src(srcPaths.js_rxjs)
.pipe(gulp.dest(destPaths.js_rxjs));
return gulp.src(srcPaths.js)
.pipe(gulp.dest(destPaths.js));
});
// Watch specified files and define what to do upon file changes
gulp.task('watch', function () {
gulp.watch([srcPaths.app, srcPaths.js], ['app', 'js']);
});
// Define the default task so it will launch all other tasks
gulp.task('default', ['app', 'js', 'watch']);
它包含五个任务:
- app_clean——此任务从我们定义的目标文件夹中删除现有文件。
- app——此任务为所有TypeScript文件编译、uglify和创建源映射,并将它们及其js.map文件一起放置到wwwroot/app文件夹中。
- js——此任务将从位于node_modules文件夹内的外部库中复制所有JavaScript文件,并将它们放置到wwwroot/js文件夹中。
- watch——此任务监视在app和js任务中定义的已更改文件。
- default——定义默认任务,以便它将启动所有其他任务。
Angular2应用
现在是时候启动我们应用程序的客户端代码了。Angular 2的组成包括:
- Angular 2组件文件
- Angular 2模块文件
- Angular 2 bootstrap文件
- HTML文件
1)组件文件
Angular 2完全基于组件。控制器和$scope不再使用。它们已被组件和指令所代替。组件是Angular 2中最基本的概念。将其视为一个类,它控制着网页的特定部分,我们可以在其中向每个用户显示一些数据并/或对其进行响应。Angular 2应用程序将几乎完全建立在用于特定目的的多个组件上:大多数组件可重复使用;其他将只使用一次。
在项目下,创建“scripts”文件夹。在“scripts”文件夹下,创建“app”文件夹。
现在,通过右键单击“app”文件夹以添加新项目来添加新的TypeScript文件。在对话框左窗格的“客户端”选项下,选择“TypeScript文件”,如下图所示。
将该文件命名为“app.component.ts”。现在,我们只编写非常基本的Hello World代码。
import { Component } from "@angular/core";
@Component({
selector: 'masterchef2',
template: '
<h1>Master Chef</h1><div>Best recipes from AngularJS 2.</div>'
})
export class AppComponent { }
代码的第一行基本上是从Angular 2库@ angular/core 导入的Component函数。该Component函数是我们需要为类定义组件的元数据。@Component下面的代码块是组件的创建。@Component表示TypeScript指令,该指令告诉Angular此类是Angular组件。注意export关键字,它将允许我们从其他组件导入它。
2)模块文件
Angular Modules提供了一种强大的方式来组织和引导任何Angular2应用程序:它们帮助开发人员将自己的组件、指令和管道集合整合为可重用的块。
每个Angular2应用程序必须至少具有一个模块,该模块通常称为根模块,并具有AppModule类名。
现在,创建一个新的TypeScript文件,并将文件命名为“app.module.ts ”。
///<reference path="../../typings/index.d.ts">
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { HttpModule } from "@angular/http";
import "rxjs/Rx";
import { AppComponent } from "./app.component";
@NgModule({
// directives, components, and pipes
declarations: [
AppComponent
],
// modules
imports: [
BrowserModule,
HttpModule
],
// providers
providers: [
],
bootstrap: [
AppComponent
]
})
export class AppModule { }
上面配置的第一行添加了对类型定义的引用,以确保我们的TypeScript编译器可以找到它。然后,我们导入应用程序所需的基本Angular2模块。您可以根据需要在此文件中添加更多Angular 2模块引用。我们还导入了rxjs库定义文件,这对编译某些Angular2库很有用。然后,我们导入了组件“AppComponent”。最后,我们声明了根NgModule:正如我们所看到的,它包含一个命名数组数组,每个数组包含一组Angular2对象,这些对象具有共同的用途:指令、组件、管道、模块和提供程序。它们的最后一个包含我们要引导的组件,在本例中为AppComponent文件。
3)Bootstrap文件
现在我们有了主要组件,让我们添加另一个TypeScript文件来创建bootstrap以运行该应用程序。右键单击“app”文件夹,然后选择“TypeScript文件”。将该文件命名为“boot.ts”。
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { AppModule } from "./app.module";
platformBrowserDynamic().bootstrapModule(AppModule);
在boot.ts中,我们引用了新的Angular捆绑包,还引用了我们先前创建的新AppModule。
4)Index.html
现在是时候创建index.html作为浏览器的入口点了,以便它可以加载客户端脚本文件并执行应用程序,并布置Angular 2用来显示它的DOM结构。右键单击“wwwroot”文件夹,然后选择“添加新项”,然后从客户端选择“HTML页面”。将该文件命名为“index.html”。
<html>
<head>
<title>Master Chef2</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Step 1. Load libraries -->
<!-- Polyfill(s) for older browsers -->
<script src="js/shim.min.js"></script>
<script src="js/zone.js"></script>
<script src="js/Reflect.js"></script>
<script src="js/system.src.js"></script>
<!-- Angular2 Native Directives -->
<script src="/js/moment.js"></script>
<!-- Step 2. Configure SystemJS -->
<script src="systemjs.config.js"></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
</head>
<!-- Step 3. Display the application -->
<body>
<!-- Application PlaceHolder -->
<masterchef2>Please wait...</masterchef2>
</body>
</html>
5)SystemJs文件
SystemJs是配置API。加载SystemJS后,可以使用配置函数SystemJS.config在SystemJS上进行配置。这是一个帮助程序函数,用于规范化配置并设置SystemJS实例上的配置属性。
SystemJS.config({ prop: 'value' })与SystemJS.prop = value几乎等同,除了将扩展配置对象,并且某些属性将被规范化以正确存储外。
该SystemJS配置加载我们的应用程序模块和组件。
现在,让我们添加systemjs配置文件。右键单击wwwroot文件夹,然后选择“添加新项”。在客户端模板下,选择“JavaScript文件”。
将以下代码复制到systemjs.config.js。
(function (global) {
System.config({
paths: {
// paths serve as alias
'npm:': 'js/'
},
// map tells the System loader where to look for things
map: {
// our app is within the app folder
app: 'app',
// angular bundles
'@angular/core': 'npm:@angular/core/bundles/core.umd.js',
'@angular/common': 'npm:@angular/common/bundles/common.umd.js',
'@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
'@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
'@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
'@angular/http': 'npm:@angular/http/bundles/http.umd.js',
'@angular/router': 'npm:@angular/router/bundles/router.umd.js',
'@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
// angular testing umd bundles
'@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js',
'@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js',
'@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js',
'@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js',
'@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js',
'@angular/http/testing': 'npm:@angular/http/bundles/http-testing.umd.js',
'@angular/router/testing': 'npm:@angular/router/bundles/router-testing.umd.js',
'@angular/forms/testing': 'npm:@angular/forms/bundles/forms-testing.umd.js',
// other libraries
'rxjs': 'npm:rxjs',
'angular2-in-memory-web-api': 'npm:angular2-in-memory-web-api',
},
// packages tells the System loader how to load when no filename and/or no extension
packages: {
app: {
main: './boot.js',
defaultExtension: 'js'
},
rxjs: {
defaultExtension: 'js'
},
'angular2-in-memory-web-api': {
defaultExtension: 'js'
}
}
});
})(this);
6)运行应用程序
右键单击“gulpfile.js ”,然后选择“任务运行器资源管理器”。单击“刷新”按钮以加载任务。然后右键单击“默认”任务以选择“运行”。
完成任务后,所有ts文件都应编译为wwwroot文件夹下的js文件。App ts文件被编译到wwwroot\app文件夹,所有其他ts软件包都转到wwwroot\js文件夹。
现在,只需单击“IIS Express”即可运行它。
工作正常。基本上测试了我们的Angular 2组件和模块已加载。
客户端ViewModel
视图模型表示您希望在视图/页面上显示的数据,无论是用于静态文本还是用于可以添加到数据库(或编辑)的输入值(如文本框和下拉列表)。它与您的域模型有所不同。它是视图的模型。我们将使用ViewModels作为数据传输对象:将数据从客户端发送到服务器,和/或反之亦然。
我们使用TypeScript定义一组类以供我们使用类型定义。换句话说,我们将不会处理原始的JSON数据和匿名对象。相反,我们将使用类型化的对象:类的实际实例。
在“scripts/app” 下创建“viewmodels”文件夹。然后右键单击“viewmodels”以添加新类型的脚本文件。它名为“recipe”,它是我们用于在视图上显示的配方视图模型。
export class Recipe {
constructor(
public Id: string,
public Name: string,
public Comments: string
) { }
}
请注意,在我们的视图模型类中,您不必包含所有属性。您需要包括的是类型脚本中需要的内容。
客户端服务
现在,我们需要设置一个客户端服务,以从Web API获取所需的数据:向API控制器发出请求。通过XMLHttpRequest(XHR)进行通信,该XMLHttpRequest提供了用于通过Angular Http客户端在客户端和服务器之间传输数据的客户端功能。
在“Scripts/app/” 下创建一个“services”文件夹。右键单击“服务”以添加新类型的脚本文件,并将其命名为“app.service.ts”。
import { Injectable } from "@angular/core";
import { Http, Response } from "@angular/http";
import { Recipe } from "../viewmodels/recipe";
import { Observable } from "rxjs/Observable";
@Injectable()
export class AppService {
constructor(private http: Http)
{ }
//URL to web api
private recipeUrl = 'api/recipes/';
getAllRecipes() {
return this.http.get(this.recipeUrl)
.map(response => response.json())
.catch(this.handleError);
}
private handleError(error: Response) {
console.error(error);
return Observable.throw(error.json().error || "Server error");
}
}
这是一个非常简单的类,只有一个main方法getAllRecipes,它基本上调用了我们之前构建的Recipes Web控制器。
更改应用程序组件
更改应用程序组件以显示从服务器检索的配方列表。
import { Component, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { Recipe } from "./viewmodels/recipe";
import { AppService } from "./services/app.service";
@Component({
selector: 'masterchef2',
templateUrl: '../partials/recipes.html'
})
//export class AppComponent { }
export class AppComponent implements OnInit {
title: string;
items: Recipe[];
errorMessage: string;
constructor(private appService: AppService) {
//called first time before the ngOnInit()
//this.title = "Master Chef Favorite Recipes";
}
ngOnInit() {
//called after the constructor and called after the first ngOnChanges()
this.title = "Master Chef Recipes";
var service = this.appService.getAllRecipes();
service.subscribe(
items => this.items = items,
error => this.errorMessage = <any>error
);
}
}
在文件的顶部,我们导入了所需的Angular类:由于我们正在创建Component,因此需要通过引用@angular/core来获得Component基类,并且还需要实现OnInit接口,因为组件需要在初始化时执行某些操作。我们引用了我们先前创建的服务来与服务器通信以获取一些数据。最后,我们导入了配方视图模型以存储值。
@component块是我们为组件设置UI的地方,包括选择器、模板和样式。我们使用templateUrl来制作部分html。
AppComponent是写在TypeScript中的类。此类包含一些属性,以及一个使用DI实例化AppService的构造函数。该ngOnInit()方法是我们从服务中获取数据的地方,该服务将在组件初始化时触发。
添加食谱模板
在“wwwroot”下,创建一个新文件夹“partials”。右键单击“partials”以添加一个名为“recipes”的新HTML。
<h2>{{title}}</h2>
<ul>
<li *ngFor="let recipe of items">
<p> {{recipe.name}} - {{recipe.comments}}</p>
<ul>
<li *ngFor="let step of recipe.recipeSteps">
<p> step {{step.stepNo}} : {{step.instructions}}</p>
<ul>
<li *ngFor="let item of step.recipeItems">
<p> {{item.name}} {{item.quantity}} {{item.measurementUnit}}</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
请注意,在recipes.html中,绑定AppComponent类中的数据,一个是{{title}},另一个是{{items}}。这两个属性已在AppComponent类中定义,title是字符串,items是配方视图模型的数组。
更改应用程序模块
因为我们添加了新的客户端服务,所以我们需要更改app.module.ts来加载它。
///<reference path="../../typings/index.d.ts">
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { HttpModule } from "@angular/http";
import { RouterModule } from "@angular/router";
import { FormsModule } from "@angular/forms";
import "rxjs/Rx";
import { AppComponent } from "./app.component";
import { AppService } from "./services/app.service";
@NgModule({
// directives, components, and pipes
declarations: [
AppComponent,
],
// modules
imports: [
BrowserModule,
HttpModule,
FormsModule,
RouterModule
],
// providers
providers: [
AppService
],
bootstrap: [
AppComponent
]
})
export class AppModule { }
启动Master Chef2
右键单击“gulpfile.js”,然后选择“任务运行器资源管理器”。单击“刷新”按钮以加载任务。然后右键单击“默认”任务以选择“运行”。完成任务后,单击“IIS Express”以运行该应用程序。
将Bootstrap样式应用于Angular 2 Front UI
1)安装Bootstrap
我们使用Bower软件包管理器来获取客户端引导程序文件。因此,要做的第一件事是右键单击我们的项目以添加一个新项目。在客户端上选择一个Bower配置文件“bower.json ”。
在bower.json中,在dependencies部分添加“bootstrap”。
{
"name": "asp.net",
"private": true,
"dependencies": {
"bootstrap": "3.3.7"
}
}
恢复完成后,Visual Studio将在 wwwroot\lib 文件夹下安装引导程序。
2)在index.html中添加boostrap链接样式
将以下行添加到index.html中。
<link href="lib/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" media="screen">
3)在recipes.html中应用bootstrap
现在,我们稍作更改,以应用一些bootstrap样式。
<div>
<h2>{{title}}</h2>
<div *ngFor="let recipe of items">
<div class="btn-group">
<button class="btn-outline-info pull-left"><h5>{{recipe.name}} - {{recipe.comments}}</h5></button>
</div>
<div *ngFor="let step of recipe.recipeSteps">
<div class="row breadcrumb">
<span>step {{step.stepNo}} : {{step.instructions}}</span>
</div>
<ul>
<li *ngFor="let item of step.recipeItems">
<p> {{item.name}} {{item.quantity}} {{item.measurementUnit}}</p>
</li>
</ul>
</div>
</div>
</div>
从IIS Express运行我们的项目。
在Angular 2中展开/折叠DOM元素
在之前的master chef 文章中,我向您展示了如何在Angular 1中进行操作。现在,我向您展示Angular 2的方法。
首先在App.Component.ts中添加扩展功能以切换“show”属性。
export class AppComponent implements OnInit {
title: string;
items: Recipe[];
errorMessage: string;
show: boolean;
constructor(private appService: AppService) {
//called first time before the ngOnInit()
//this.title = "Master Chef Favorite Recipes";
}
ngOnInit() {
//called after the constructor and called after the first ngOnChanges()
this.title = "Master Chef Recipes";
var service = this.appService.getAllRecipes();
service.subscribe(
items => this.items = items,
error => this.errorMessage = <any>error
);
}
public Expand() {
this.show = !this.show;
}
}
在recipes.html中,我添加了单击事件处理程序以调用扩展功能。
<div class="btn-group">
<button class="btn-outline-info pull-left" (click)="Expand()"><h5>{{recipe.name}} - {{recipe.comments}}</h5></button>
</div>
您需要注意两件事。
- 括号表示我们正在等待一个事件。代替ng-click="fn()"和ng-mouseover="fn()",我们仅使用(click)="fn()"和(mouseover)="fn()"。
- 方括号表示属性。而不是ng-src="ctrl.value"和ng-style="{ 'width': ctrl.value }",我们现在可以轻松地做到[src]="value"和[width]="value"。
然后使用ng-if检查show属性来隐藏或显示子元素。
<div *ngIf="show">
<div *ngFor="let step of recipe.recipeSteps">
<div class="row breadcrumb">
<span>step {{step.stepNo}} : {{step.instructions}}</span>
</div>
<ul>
<li *ngFor="let item of step.recipeItems">
<p> {{item.name}} {{item.quantity}} {{item.measurementUnit}}</p>
</li>
</ul>
</div>
</div>
如何使用示例代码
确保VS2015更新3已安装了最新的ASP.NET Web工具和TypeScript2。下载源代码。打开MasterChef2WebApp.sln,然后重新生成解决方案。在Task Runner Explorer中,运行默认任务。所有任务成功完成后,通过单击“IIS Express”启动项目。
结论
在本文中,我向您展示了如何在ASP.NET Core的上下文中从头开始构建数据驱动的Angular 2应用程序。我们还学习了如何在Angular2应用中创建Web API并与之通信。