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

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (13投票s)

2016年5月29日

CPOL

3分钟阅读

viewsIcon

33482

在本文中,您将学习如何使用 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>&copy; 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'
);

关键点在这里:controllercontrollerAs,这与 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">&times;</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 进行 getpost 请求。
  • 当我们在 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 中,ApiControllerController 合并为一个称为 "Controller" 的类,通过请求映射模型,我们应该包含 [FromBody] 来将数据映射到我们的模型。

完成所有操作后,按 F5 查看结果!

步骤 1:按下 Create 按钮并填写数据

步骤 2:按下 submit 按钮并查看结果,新记录已添加。

使用 CookieAuthenticationOptions 进行身份验证

背景

  • 任何用户在加入我们的系统之前都必须经过身份验证
    • 用户通过用户名 + 密码进行身份验证
  • 登录成功后,用户可以调用我们的 API
  • 屏幕过渡:登录 -> 主屏幕 -> 子屏幕。如果登录失败或 cookie 超时,则转回登录屏幕。

实现

  1. 客户端

    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>&copy; 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 -> 转回登录页面。

  2. 服务器端

    创建 "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 进行身份验证
© . All rights reserved.