使用 MVC 6、Web API 2、ASPNET 5 和 Gulp 的 Angular JS 应用程序 – 第 1 部分






4.88/5 (51投票s)
使用 MVC 6 中的 Web API 构建 Angular JS 应用程序。
引言
在本文中,我将解释如何为 Angular JS 中的单页应用程序 (SPA) 设置 ASP.NET 5 项目。我已使用 MVC 6 Web API 显示一些静态数据,最终我将使用数据库通过 Web API 存储和显示数据。我还会展示如何使用 gulp、bower 和 npm 自动复制前端 JavaScript 文件。在后续模块中,我将描述如何合并和最小化前端 JavaScript 文件。
在我的演示中,我使用的是 Visual Studio 2015 RC,这是用于跨平台开发的下一代 Visual Studio 的最新预发布版本。我还会使用 Entity Framework 7 和 DNX (.NET 执行环境) 来迁移数据库并在不同的环境中运行网站。
背景
由于近年来前端工具和技术的进步,单页应用程序 (SPA) 现在已是众所周知的术语。其中,Angular JS 是构建 SPA 最受欢迎的选择。微软最近发布的 Visual Studio 2015 RC 是开发 SPA 的绝佳平台,可以在其中轻松管理和维护应用程序的前端工具。此外,MVC 6 中的 Web API 2 被用作后端服务,可以高效地支持从前端调用它。
我将全程使用 Visual Studio 2015 RC、MVC 6 Web API2、gulp、bower 和 npm 来进行前端和后端开发。
创建项目
首先,我打开了 Visual Studio 2015 RC,从开始页面中,我选择了新建项目,然后从可用模板中,我选择了 ASP.NET Web 应用程序。我将项目命名为 BooksAngularApp
。
点击确定后,会出现模板选择向导,我将从可用的 ASP.NET 5 预览模板中选择网站。我选择此模板的原因是它为我们提供了一个有用的框架,而不是从空模板开始。
需要注意的是,网站默认带有 gulp 任务运行器,我将使用它来合并和最小化前端 JavaScript 和 CSS 文件。
点击确定后,我创建了以下项目,项目结构和预览页面如下所示
任务运行器
如果我们查看解决方案资源管理器中的项目结构,可以看到 bower 文件夹中预安装的包,例如 bootstrap、jquery、hammer 等。此外,我们还可以看到 npm 文件夹,其中已安装 gulp 和 rimraf 作为前端工具,用于管理前端脚本,特别是 JavaScript 和 CSS 文件。
现在让我们看一下 gulpfile.js,它将用于管理我们的前端代码或脚本。
var gulp = require("gulp"),
rimraf = require("rimraf"),
fs = require("fs");
eval("var project = " + fs.readFileSync("./project.json"));
var paths = {
bower: "./bower_components/",
lib: "./" + project.webroot + "/lib/"
};
gulp.task("clean", function (cb) {
rimraf(paths.lib, cb);
});
gulp.task("copy", ["clean"], function () {
var bower = {
"bootstrap": "bootstrap/dist/**/*.{js,map,css,ttf,svg,woff,eot}",
"bootstrap-touch-carousel": "bootstrap-touch-carousel/dist/**/*.{js,css}",
"hammer.js": "hammer.js/hammer*.{js,map}",
"jquery": "jquery/jquery*.{js,map}",
"jquery-validation": "jquery-validation/jquery.validate.js",
"jquery-validation-unobtrusive":
"jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"
}
for (var destinationDir in bower) {
gulp.src(paths.bower + bower[destinationDir])
.pipe(gulp.dest(paths.lib + destinationDir));
}
});
我们可以看到脚本运行一些任务,在开发过程中文件发生任何更改时,可以复制和清理 wwwroot 文件夹中的 JavaScript 和 CSS 文件。
让我们看看这些是如何发生的。我从 视图->其他窗口->任务运行器 中打开了任务运行器。
我们可以看到 gulpfile.js 中定义的 clean 和 copy 任务。在我们运行它们中的任何一个之前,我们可以看到一个 lib 文件夹默认包含所有 JavaScript 和 CSS 文件的副本。
我运行了 clean 任务,它清除了 wwwroot 目录中的 lib 文件夹,这意味着 gulp
被用于运行 clean 任务以删除静态文件。
我运行了 copy
任务,lib 文件夹又回到了复制的文件。
安装 Angular 包
对于我们的 SPA,我将使用三个 Angular 包:angular
、angular-route
和 angular-resource
。目前,默认的 bower.json 文件如下所示
>
{
"name": "ASP.NET",
"private": true,
"dependencies": {
"bootstrap": "3.0.0",
"jquery": "1.10.2",
"jquery-validation": "1.11.1",
"jquery-validation-unobtrusive": "3.2.2",
"hammer.js": "2.0.4",
"bootstrap-touch-carousel": "0.8.0"
}
}
我已添加以下三个 angular
包
{
"name": "ASP.NET",
"private": true,
"dependencies": {
"bootstrap": "3.0.0",
"jquery": "1.10.2",
"jquery-validation": "1.11.1",
"jquery-validation-unobtrusive": "3.2.2",
"hammer.js": "2.0.4",
"bootstrap-touch-carousel": "0.8.0",
"angular": "*",
"angular-route": "*",
"angular-resource": "*"
}
}
添加三行 angular
后,我尚未保存 bower.json 文件,并且在解决方案资源管理器的 Bower 文件夹中,没有 angular
模块。
保存文件后,几秒钟后我们可以在 bower 文件夹的依赖项列表中看到 angular 组件。
您可能会看到 bower 文件夹如下所示,其中未安装 angular
模块
如果它们未安装,请右键单击 Bower 文件夹并点击恢复包,它将恢复添加到 bower.json 文件中的所有未安装包。
为 Angular 脚本创建 Gulp 任务
现在我已经安装了 angular
包,我们需要更新任务运行器,以便将 angular 文件与其他 bower 组件一起复制到 lib 文件夹。
我已更新 copy
任务,如下所示
gulp.task("copy", ["clean"], function () {
var bower = {
"bootstrap": "bootstrap/dist/**/*.{js,map,css,ttf,svg,woff,eot}",
"bootstrap-touch-carousel": "bootstrap-touch-carousel/dist/**/*.{js,css}",
"hammer.js": "hammer.js/hammer*.{js,map}",
"jquery": "jquery/jquery*.{js,map}",
"jquery-validation": "jquery-validation/jquery.validate.js",
"jquery-validation-unobtrusive":
"jquery-validation-unobtrusive/jquery.validate.unobtrusive.js",
"angular": "angular/angular*.{js,map}",
"angular-route": "angular-route/angular-route*.{js,map}",
"angular-resource": "angular-resource/angular-resource*.{js,map}"
}
现在我再次运行 copy
任务,可以看到 angular 文件已复制到 wwwroot 的 lib 文件夹中。
我已配置 copy
和 copyapp
任务在应用程序的每次构建后运行,以确保更改被复制。
现在我们已准备好开始使用 SPA BooksAngularApp
。
创建数据模型和 Web API
对于 SPA,我将使用一个虚构的书店,我们可以在其中查看书籍列表及其详细信息。在此模块中,我将使用一些静态数据作为书籍项目。为此,我需要一个数据模型,用于访问和显示书籍。
我在项目 Models 文件夹中添加了一个名为 Book
的新类,模型如下所示
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
namespace BooksAngularApp.Models
{
/// <summary>
/// The entity class with Book properties
/// </summary>
public class Book
{
[Key]
public int Id { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public string Type { get; set; }
public DateTime DatePublished { get; set; }
public decimal Price { get; set; }
}
}
之后,我创建了一个名为 BooksController
的 MVC 控制器类,它将作为我们数据访问的 API 控制器。
默认情况下,Controller
类如下所示
我已将该类更改为 API 控制器,通过添加...
[Route("api/[controller]")]
...在类定义之前,这样通过 Route
属性,它将充当 API 控制器。
我已添加 HttpGet
方法以显示静态书籍列表,如下所示
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc;
using BooksAngularApp.Models;
namespace BooksAngularApp.Controllers
{
[Route("api/[controller]")]
public class BooksController : Controller
{
[HttpGet]
public IEnumerable<Book> Get()
{
return new List<Book>
{
new Book {Id=1, Title="Wonders of the Sky",
Author="Martin James", DatePublished=Convert.ToDateTime("1/1/2013"),
Type="Science",Price=23.23m },
new Book {Id=2, Title="Secrets of the Mind ",
Author="Allan Sue ", DatePublished=Convert.ToDateTime("2/1/2011"),
Type="Psychology", Price=12.50m },
new Book {Id=3, Title="We are Alive",
Author="Dick Smith", DatePublished=Convert.ToDateTime("2/11/2010"),
Type="Science Fiction", Price=21.25m } ,
new Book {Id=4, Title="Last day of the world",
Author="Martin James", DatePublished=Convert.ToDateTime("1/1/2013"),
Type="History", Price=10.40m },
};
}
}
}
为了查看我创建的 API 控制器是否正常工作,让我们从浏览器运行 API
我们可以看到数据以 JSON 格式从 API 控制器显示,这意味着我们已准备好使用 API 来显示数据。
编写 Angular 代码
我在应用程序根目录中创建了一个 app 文件夹,我将在此处放置所有与 Angular 相关的模块,例如控制器、服务等。
我添加了 app.js 作为第一个模块,如下所示
(function () {
'use strict';
angular.module('booksApp', ['booksServices']);
})();
booksApp
Angular 模块将依赖于名为 booksServices
的服务,我将立即定义它。
我在 app 文件夹中创建了一个名为 services 的新文件夹,并添加了 booksServices.js,如下所示
(function () {
'use strict';
var booksServices = angular.module('booksServices', ['ngResource']);
booksServices.factory('Books', ['$resource',
function ($resource) {
return $resource('/api/books/', {}, {
query: { method: 'GET', params: {}, isArray: true }
});
}]);
})();
该服务将注入 ngResource
作为依赖项,它最终将调用 API 控制器以从 API 获取数据。
现在我们需要另一个 Angular 模块,我们将其命名为 booksController
,我已在 app 文件夹中的 controllers 文件夹中创建,命名为 booksController.js。
(function () {
'use strict';
angular
.module('booksApp')
.controller('booksController',booksController)
booksController.$inject = ['$scope', 'Books'];
function booksController($scope, Books)
{
$scope.books = Books.query();
}
})();
在这里我们可以看到,控制器注入了 scope
对象,该对象返回 API 的查询结果。
为应用程序脚本编写任务
我们需要编写任务,将文件复制到 wwwroot 文件夹,就像我们之前对其他 JavaScript 文件所做的那样。
var paths = {
bower: "./bower_components/",
lib: "./" + project.webroot + "/lib/",
app: "./" + project.webroot + "/app/",
srcapp: "./app/",
};
gulp.task("cleanappp", function (cb) {
rimraf(paths.app, cb);
});
gulp.task("copyapp",["cleanappp"], function () {
var app = {
"controllers": "controllers/booksController.js",
"services": "services/booksServices*.js",
"/": "app.js"
}
for (var destinationDir in app) {
gulp.src(paths.srcapp + app[destinationDir])
.pipe(gulp.dest(paths.app + destinationDir));
}
});
我已在 paths 变量中添加了 app 和 srcapp,以定义应用程序脚本的源和目标。我添加了一个名为 cleanapp 的新任务,它将用于清理 wwwroot 中的 app 文件夹。最后,我添加了另一个名为 copyapp 的任务,它注入了 cleanapp 任务,这意味着在复制应用程序脚本之前,它将首先清理文件夹,然后复制所有脚本文件。
<!DOCTYPE html>
<html ng-app="booksApp">
<head>
<meta charset="utf-8" />
<title>List of Books</title>
<link href="lib/bootstrap/css/bootstrap.css" rel="stylesheet" />
<link href="lib/bootstrap/css/bootstrap-theme.css" rel="stylesheet" />
<script src="lib/jquery/jquery.min.js"></script>
<script src="lib/angular/angular.js"></script>
<script src="lib/bootstrap/js/bootstrap.js"></script>
<script src="lib/angular-resource/angular-resource.js"></script>
<script src="app/app.js"></script>
<script src="app/controllers/booksController.js"></script>
<script src="app/services/booksServices.js"></script>
</head>
<body ng-cloak>
<div ng-controller="booksController">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>Type</th>
<th>Date Published</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="book in books">
<td>{{book.Title}}</td>
<td>{{book.Author}}</td>
<td>{{book.Type}}</td>
<td>{{book.DatePublished|date:"dd/MM/yyyy"}}</td>
<td>${{book.Price}}</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
在这里,网站同时运行 IIS Express 和 WebListener。
历史
- 2015 年 5 月 16 日:首次提交
- 2015 年 5 月 24 日:向文章添加了 ZIP 解决方案
源代码
本文的源代码可在 GitHub 上获取:https://github.com/mostafaasad/ASPNET5/tree/master/BooksAngular-Part1/BooksAngularApp
下一篇
在下一个模块中,我将探讨数据的搜索和过滤,以及使用 Entity Framework 7 和 DNX 进行数据库迁移。