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进行身份验证

