ASP.NET Core RC2 使用 WEB API 和 AngularJS - 第二部分






4.86/5 (13投票s)
在本文中,您将学习如何使用 WEB API 和 AngularJS 来使用 ASP.NET Core RC2。
引言
必备组件
- Visual Studio 2015:您可以在 这里 下载。
- .NET Core SDK:从这个 链接 下载 .NET Core SDK。
- .NET Framework 4.5.2:从这个 链接 下载 .NET Framework 4.5.2。
- .NodeJS:从这个 链接 下载 NodeJS。
- 源代码:从这个 链接 下载源代码。
在本部分,我们将详细介绍如何
- 创建学生组件
- 通过 Web API 与服务器通信
- 创建表单并将数据推送到服务器
- 使用 AspNetCore
CookieAuthenticationOptions
进行身份验证
背景
在阅读本部分之前,您应该先阅读 第一部分。
Using the Code
让我们开始吧!
添加 student
组件
- wwwroot/app/components/student/student.html
- wwwroot/app/components/student/student.controller.html
student.html 的内容
<h2>Student List</h2>
<table class="table table-striped">
<thead>
<tr>
<th>Firstname</th>
<th>Lastname</th>
<th>Email</th>
</tr>
</thead>
<tbody>
<tr>
<td>John</td>
<td>Doe</td>
<td>john@example.com</td>
</tr>
<tr>
<td>Mary</td>
<td>Moe</td>
<td>mary@example.com</td>
</tr>
<tr>
<td>July</td>
<td>Dooley</td>
<td>july@example.com</td>
</tr>
</tbody>
</table>
student.controller.js 的内容
(function () {
'use strict';
angular
.module('app')
.controller('StudentController', MainController);
function MainController($scope) {
var vm = this;
}
})();
修改 main.html 为
<h2>This application consists of:</h2>
<ul>
<li>Sample pages using ASP.NET Core with
AngularJS, NPM, Gulp, Bower, Bootstrap, Jquery</li>
<li>More detail <a href="https://codeproject.org.cn/script/Articles/
ArticleVersion.aspx?waid=209236&aid=1102877">here</a></li>
</ul>
修改 index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AspNetCoreSPA</title>
<script type="text/javascript">
var site = site || {};
</script>
<!-- bower:css -->
<!-- endbower -->
<!-- inject:css -->
<!-- endinject -->
</head>
<body ng-app="app">
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="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>
</button>
<a ui-sref="home" class="navbar-brand">AspNetCoreSPA</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a ui-sref="home">Home</a></li>
<li><a ui-sref="student">Student</a></li>
</ul>
</div>
</div>
</div>
<div class="container body-content">
<div class="jumbotron">
<h1>AspNetCoreSPA</h1>
<p class="lead">Welcome to AspNetCoreSPA</p>
</div>
<div class="row">
<div ui-view></div>
</div>
<hr />
<footer>
<p>© 2016 - Toan Manh Nguyen</p>
</footer>
</div>
<!-- bower:js -->
<!-- endbower -->
<!-- inject:js -->
<!-- endinject -->
</body>
</html>
关于路由的解释
ui-sref="home"
将映射到
$stateProvider
.state('home', {
url: '/',
templateUrl: 'app/main/main.html',
controller: 'MainController',
controllerAs: 'mainCtrl'
})
index.route.js 的完整内容
(function () {
'use strict';
angular
.module('app')
.config(routerConfig);
routerConfig.$inject = ['$stateProvider', '$urlRouterProvider'];
function routerConfig($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('home', {
url: '/',
templateUrl: 'app/main/main.html',
controller: 'MainController',
controllerAs: 'mainCtrl'
})
.state('student', {
url: '/student',
templateUrl: 'app/components/student/student.html',
controller: 'StudentController',
controllerAs: 'studentCtrl'
});
}
})();
现在按 F5 键查看新结果
点击 Student
菜单
与 第一部分 相同的结果,但它有一些导航,现在我们将通过 http 从服务器获取学生列表并在 student.html 中加载结果。
首先,创建 StudentController
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
namespace AspNetCoreSPA.Web.Controllers
{
[Route("api/student")]
public class StudentController : Controller
{
private List<Student> _students = new List<Student>
{
new Student { FirstName = "John", LastName = "Doe", Email = "john@example.com"},
new Student { FirstName = "Mary", LastName = "Moe", Email = "mary@example.com"},
new Student { FirstName = "July", LastName = "Dooley", Email = "july@example.com"}
};
[Route("getAll")]
public IEnumerable<Student> GetAll()
{
return _students;
}
}
public class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
}
我们有 GetAll
方法,该方法返回 student
列表(JSON 格式)。
修改 student.html
<h2>Student List</h2>
<table class="table table-striped">
<thead>
<tr>
<th>Firstname</th>
<th>Lastname</th>
<th>Email</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="student in studentCtrl.students">
<td>{{student.FirstName}}</td>
<td>{{student.LastName}}</td>
<td>{{student.Email}}</td>
</tr>
</tbody>
</table>
您看到 "studentCtrl.students
" 了吗?如果我们之前学习过 Angular,它应该有 ng-controller="StudentController
作为 studentCtrl
" 对吗?但请看一下 index.route.js
.state('student', {
url: '/student',
templateUrl: 'app/components/student/student.html',
controller: 'StudentController',
controllerAs: 'studentCtrl'
);
关键点在这里:controller
和 controllerAs
,这与 ng-controller
相同。
接下来,我们修改 student.controller.js
(function () {
'use strict';
angular
.module('app')
.controller('StudentController', StudentController);
StudentController.$inject = ['$http', '$http'];
function StudentController($scope, $http) {
var vm = this;
vm.students = [];
$http.get("api/student/getAll").then(function(response)
{ vm.students = response.data; });
}
})();
按 F5,点击 Student 菜单并查看结果
与之前完全相同!
接下来,我们将创建一个 Create 按钮并创建一个新的 student
。
在执行此操作之前,请将这些代码添加到 index.module.js:每当 ng-view
内容更改时,这会自动清除缓存。
angular.module('app').run(function ($rootScope, $templateCache) {
$rootScope.$on('$viewContentLoaded', function () {
$templateCache.removeAll();
});
});
修改 student.html
<h2>Student List</h2>
<table class="table table-striped">
<thead>
<tr>
<th>Firstname</th>
<th>Lastname</th>
<th>Email</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="student in studentCtrl.students">
<td>{{student.FirstName}}</td>
<td>{{student.LastName}}</td>
<td>{{student.Email}}</td>
</tr>
</tbody>
</table>
<input type="button" class="btn btn-default" value="Create"
data-toggle="modal" data-target="#formCreateStudent"/>
<form id="formCreateStudent" class="modal fade" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close"
data-dismiss="modal">×</button>
<h4 class="modal-title">Create new student</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label for="firstName">First Name</label>
<input type="text" class="form-control" id="firstName"
placeholder="First Name"
ng-model="studentCtrl.createStudentInput.FirstName">
</div>
<div class="form-group">
<label for="lastName">Last Name</label>
<input type="text" class="form-control" id="lastName"
placeholder="Last Name"
ng-model="studentCtrl.createStudentInput.LastName">
</div>
<div class="form-group">
<label for="emailAddress">Email address</label>
<input type="email" class="form-control" id="emailAddress"
placeholder="Email" ng-model="studentCtrl.createStudentInput.Email">
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-success"
ng-click="studentCtrl.createStudent()">Submit</button>
<button type="button" class="btn btn-default"
data-dismiss="modal">Close</button>
</div>
</div>
</div>
</form>
请注意,我们使用 bootstrap modal 来显示对话框。
修改 student.controller.js
(function () {
'use strict';
angular
.module('app')
.controller('StudentController', StudentController);
StudentController.$inject = ['$scope', '$http'];
function StudentController($scope, $http) {
var vm = this;
vm.students = [];
vm.createStudentInput = {};
vm.createStudent = function () {
$http.post("api/student/createStudent", JSON.stringify(vm.createStudentInput))
.then(function (response) {
// Re-load data
vm.students = response.data;
// Close dialog
$('#formCreateStudent').modal('toggle');
});
}
$http.get("api/student/getAll")
.then(function(response) {
vm.students = response.data;
});
}
})();
解释
- 我们使用
$http
进行get
和post
请求。 - 当我们在 Create 表单中点击 Submit 按钮时,将调用
vm.createStudent
。 $http.post
或$http.get
是一个快捷方法(更多详情请参见 此处)。- 在
student
创建后,我们还需要重新加载数据并关闭 modal。 -
vm.students = response.data; $('#formCreateStudent').modal('toggle');
让我们看看现在的 StudentController
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
namespace AspNetCoreSPA.Web.Controllers
{
[Route("api/student")]
public class StudentController : Controller
{
private static List<Student> _students = new List<Student>
{
new Student { FirstName = "John", LastName = "Doe", Email = "john@example.com"},
new Student { FirstName = "Mary", LastName = "Moe", Email = "mary@example.com"},
new Student { FirstName = "July", LastName = "Dooley", Email = "july@example.com"}
};
[Route("getAll")]
[HttpGet]
public IActionResult GetAll()
{
return Json(_students);
}
[Route("createStudent")]
[HttpPost]
public IActionResult CreateStudent([FromBody] Student student)
{
if (!ModelState.IsValid)
{
return BadRequest();
}
_students.Add(student);
return Json(_students);
}
}
public class Student
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string Email { get; set; }
}
}
在 AspNetCore 中,ApiController
和 Controller
合并为一个称为 "Controller
" 的类,通过请求映射模型,我们应该包含 [FromBody]
来将数据映射到我们的模型。
完成所有操作后,按 F5 查看结果!
步骤 1:按下 Create 按钮并填写数据
步骤 2:按下 submit 按钮并查看结果,新记录已添加。
使用 CookieAuthenticationOptions 进行身份验证
背景
- 任何用户在加入我们的系统之前都必须经过身份验证
- 用户通过用户名 + 密码进行身份验证
- 登录成功后,用户可以调用我们的 API
- 屏幕过渡:登录 -> 主屏幕 -> 子屏幕。如果登录失败或 cookie 超时,则转回登录屏幕。
实现
- 客户端
Index.html:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>AspNetCoreSPA</title> <script type="text/javascript"> var site = site || {}; </script> <!-- bower:css --> <!-- endbower --> <!-- inject:css --> <!-- endinject --> </head> <body ng-app="app"> <div ui-view="login"></div> <div ui-view="main"></div> <!-- bower:js --> <!-- endbower --> <!-- inject:js --> <!-- endinject --> </body> </html>
<div ui-view="login"></div>
:Login.html 将在此处渲染。<div ui-view="main"></div>
:Main.html 将在此处渲染(Main.html 类似于 _Layout.cshtml)让我们看看主要部分
main.html
:正如我之前所说,此页面是我们的主页面,所有子页面都将在其中渲染<div ui-view="content"></div>
<div class="navbar navbar-inverse navbar-fixed-top"> <div class="container"> <div class="navbar-header"> <button type="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> </button> <a ui-sref="home" class="navbar-brand">AspNetCoreSPA</a> </div> <div class="navbar-collapse collapse"> <ul class="nav navbar-nav"> <li><a ui-sref="main">Home</a></li> <li><a ui-sref="student">Student</a></li> </ul> </div> </div> </div> <div class="container body-content"> <div class="jumbotron"> <h1>AspNetCoreSPA</h1> <p class="lead">Welcome to AspNetCoreSPA</p> </div> <div ui-view="content"></div> <hr /> <footer> <p>© 2016 - Toan Manh Nguyen</p> </footer> </div>
main.controller.js:
(function () { 'use strict'; angular .module('app') .controller('MainController', MainController); MainController.$inject = ['$scope']; function MainController($scope) { var vm = this; } })();
main.route.js:请参阅 此处 了解嵌套状态和嵌套视图的约定 "
content@main
"。(function () { 'use strict'; angular .module('app') .config(routerConfig); routerConfig.$inject = ['$stateProvider']; function routerConfig($stateProvider) { $stateProvider .state('main', { url: '/', views: { 'main': { templateUrl: 'app/main/main.html', controller: 'MainController', controllerAs: 'vm' }, 'content@main': { templateUrl: 'app/components/home/home.html', controller: 'HomeController', controllerAs: 'vm' } } }); } })();
回到旧的 ASP.NET MVC,概念是:_Layout.cshtml + Home.cshtml(我们使用 layout 作为主页面,第一个页面是 home)。现在在 Angular js 中,概念是相似的:我们总是第一次在 main.html 中渲染 home.html。
登录部分
login.html
<div class="container"> <div class="row"> <div class="col-sm-6 col-md-4 col-md-offset-4"> <h1 class="text-center login-title">Login to our System</h1> <div class="account-wall"> <form class="form-signin"> <input type="text" ng-model="vm.loginInfo.UserName" class="form-control" placeholder="Username" required autofocus> <input type="password" ng-model="vm.loginInfo.Password" class="form-control" placeholder="Password" required> <input class="btn btn-lg btn-primary btn-block" type="button" ng-click="vm.login()" value="Sign in" /> </form> </div> </div> </div> </div>
login.controller.js:
(function () { 'use strict'; angular .module('app') .controller('LoginController', LoginController); LoginController.$inject = ['$scope', '$http', '$state', 'auth0Service']; function LoginController($scope, $http, $state, auth0Service) { var vm = this; vm.loginInfo = { UserName: "test01", Password: "Qwer!@#12345" }; vm.login = function () { auth0Service.login(vm.loginInfo, function (response) { if (response == "OK") { auth0Service.authenticate(); $state.go('main'); } }); } } })();
请注意,当登录状态为 "
OK
" 时,我们将转到 "main
" 页面:$state.go('main');
login.route.js:
(function () { 'use strict'; angular .module('app') .config(routerConfig); routerConfig.$inject = ['$stateProvider']; function routerConfig($stateProvider) { $stateProvider .state('login', { url: '/login', views: { 'login@': { templateUrl:'app/login/login.html', controller: 'LoginController', controllerAs: 'vm' } } }); } })();
但是如果状态是 401 呢?会发生什么?
让我们看看 site.ng.js
'responseError': function (ngError) { var state = $injector.get('$state'); var auth0 = $injector.get('auth0Service'); var error = { message: ngError.data || site.ng.http.defaultError.message, details: ngError.statusText || site.ng.http.defaultError.details, responseError: true } if (ngError.status === 401) { auth0.clear(); state.go("login"); } else { site.ng.http.showError(error); } return $q.reject(ngError); }
我们检查状态是否为 401 -> 转回登录页面。
- 服务器端
创建 "
MyIdentity
" 类using System; using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; namespace AspNetCoreSPA.Web.Configurations { public static class MyIdentity { public static void UseMyIdentity(this IApplicationBuilder app) { var applicationCookie = new CookieAuthenticationOptions { AutomaticAuthenticate = true, AutomaticChallenge = true, CookieName = "AspNetCoreSPA", Events = new CookieAuthenticationEvents { OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync, OnRedirectToLogin = ctx => { // If request comming from web api // always return Unauthorized (401) if (ctx.Request.Path.StartsWithSegments("/api") && ctx.Response.StatusCode == (int)HttpStatusCode.OK) { ctx.Response.StatusCode = (int)HttpStatusCode.Unauthorized; } else { ctx.Response.StatusCode = (int)HttpStatusCode.NotFound; } return Task.FromResult(0); } }, // Set to 1 hour ExpireTimeSpan = TimeSpan.FromHours(1), // Notice that if value = false, // you can use angular-cookies ($cookies.get) to get value of Cookie CookieHttpOnly = true }; IdentityOptions identityOptions = app.ApplicationServices.GetRequiredService <IOptions<IdentityOptions>>().Value; identityOptions.Cookies.ApplicationCookie = applicationCookie; app.UseCookieAuthentication(identityOptions.Cookies.ExternalCookie); app.UseCookieAuthentication(identityOptions.Cookies.ApplicationCookie); } } }
在 Startup.cs 中,添加
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); app.UseStaticFiles(); app.UseMyIdentity(); app.UseMvcWithDefaultRoute(); }
从现在开始,如果您想保护任何控制器或操作,只需使用
[Authorize]
属性using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace AspNetCoreSPA.Web.Controllers { [Produces("application/json")] [Route("api/student")] [Authorize] public class StudentController : Controller { private static List<Student> _students = new List<Student> { new Student { FirstName = "John", LastName = "Doe", Email = "john@example.com"}, new Student { FirstName = "Mary", LastName = "Moe", Email = "mary@example.com"}, new Student { FirstName = "July", LastName = "Dooley", Email = "july@example.com"} }; [Route("getAll"), HttpGet] public IActionResult GetAll() { return Json(_students); } [Route("createStudent"), HttpPost] public IActionResult CreateStudent([FromBody] Student student) { if (!ModelState.IsValid) { return BadRequest(); } _students.Add(student); return Json(_students); } [Route("searchStudent"), HttpGet] public IActionResult Search([FromQuery] string firstName) { return Json(_students.Where (student => student.FirstName.Equals(firstName))); } } public class Student { [Required] public string FirstName { get; set; } [Required] public string LastName { get; set; } [Required] public string Email { get; set; } } }
历史
- 2016/05/29 - 创建了第二部分
- 2016/06/04 - 更新了使用
CookieAuthenticationOptions
进行身份验证