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

从 AngularJS 客户端使用 Web API 2(独立用户帐户 + CORS 已启用)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (37投票s)

2014年3月11日

CPOL

6分钟阅读

viewsIcon

178423

downloadIcon

5320

如何从启用了 CORS 的 AngularJS 客户端使用 ASP.NET Web API 2(独立用户帐户)来颁发 Bearer 令牌。

Final project running...

未使用。

我已将代码升级为使用 VS 2013 更新 3 的新模板和“Microsoft.AspNet.Identity.Core”v2.1 DLL,它具有许多相对于 v1 的新功能和增强功能,例如将用户表 ID 从字符串更改为整数的灵活性,我已完成此操作,您可以在项目代码“WebApiAngularJs(Identity.Core v2).zip”中找到它。

引言

本教程将展示如何使用 AngularJS 客户端来调用启用了 CORS 的 Web API 2(独立用户帐户身份验证模式),并从使用Authorize 属性的 API Controller 请求数据。

为实现此目的,我们将面临的主要问题是:

  • 如何配置 Angular $http 服务,将请求数据序列化为 URL 编码。
  • 如何在服务器端启用跨域资源共享 (CORS),以允许来自不同域(来源)的请求。

我将逐步介绍项目设置,或者您可以跳过此步骤并下载源代码。

本教程包含以下部分:

 

必备组件

 

创建 Web API 项目

我将使用新的VS 2013 Web Api 模板来引导我们的项目。

启动 Visual Studio 2013。从文件菜单中,单击新建项目。在新建项目对话框中,单击左侧窗格中的 Web,然后在中间窗格中单击 ASP.NET Web 应用程序。将项目名称输入为WebApi2Service,然后单击“确定”。

新建 ASP.NET 项目对话框中,选择“Web API”模板。

单击更改身份验证。在更改身份验证对话框中,选择个人用户帐户。单击“确定”。

 

新建 ASP.NET 项目对话框中单击“确定”以创建项目。

 

此时,如果我们尝试运行应用程序并从 /api/values 控制器请求数据,我们将收到错误 Authorization has been denied for this request,这是因为 ValuesController 被Authorize 属性装饰,这强制该控制器的用户进行身份验证。

因此,下一步是创建一个 Web 客户端,用于注册/登录用户并从 ValuesController 请求数据。

 

创建 AngularJS 客户端应用程序

我们的 AngularJS 客户端项目 UI 将非常基础,并将使用最少的代码来实现以下目标:

  • 注册新用户。
  • 登录并身份验证已注册用户,并检索 bearer 令牌。
  • 使用颁发的令牌请求需要用户身份验证的受保护 Web API 控制器的数据。

我们将创建一个单独的项目来处理 CORS 问题,并查看如何解决它。.

在解决方案资源管理器中,右键单击解决方案。选择添加,然后选择新建项目

添加新项目对话框中,与之前一样,选择ASP.NET Web 应用程序。将项目命名为AngularJsClient。单击“确定”。

在新建 ASP.NET 项目对话框中。单击“确定”。

现在我们需要安装 AngularJs 包,所以在 Visual Studio 中,从工具菜单中,选择库包管理器,然后选择程序包管理器控制台。在程序包管理器控制台中,键入以下命令:

Install-Package AngularJs.Core -ProjectName AngularJsClient

在解决方案资源管理器中,右键单击 AngularJsClient 项目。选择添加,然后选择JavaScript 文件

为项指定名称对话框中,将文件命名为 app.js。单击“确定”。

我们需要考虑的第一件重要事情是,新的 Web API 2 要求请求数据进行 URL 编码,这意味着我们必须将 content-type 标头设置为“application/x-www-form-urlencoded;charset=utf-8”,并且我们还需要序列化发送的数据,以便服务器能够正确接收数据,因此我们将配置 $httpProvider 服务 post 方法来执行此操作,因此我们的 app.js 文件将如下所示:

(function () {
    'use strict';
    // Module name is handy for logging
    var id = 'app';
    // Create the module and define its dependencies.
    var app = angular.module('app', [
    ]);
    app.config(['$httpProvider', function ($httpProvider) {
        // Use x-www-form-urlencoded Content-Type
        $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
        // Override $http service's default transformRequest
        $httpProvider.defaults.transformRequest = [function (data) {
            /**
             * The workhorse; converts an object to x-www-form-urlencoded serialization.
             * @param {Object} obj
             * @return {String}
             */
            var param = function (obj) {
                var query = '';
                var name, value, fullSubName, subName, subValue, innerObj, i;
                for (name in obj) {
                    value = obj[name];
                    if (value instanceof Array) {
                        for (i = 0; i < value.length; ++i) {
                            subValue = value[i];
                            fullSubName = name + '[' + i + ']';
                            innerObj = {};
                            innerObj[fullSubName] = subValue;
                            query += param(innerObj) + '&';
                        }
                    }
                    else if (value instanceof Object) {
                        for (subName in value) {
                            subValue = value[subName];
                            fullSubName = name + '[' + subName + ']';
                            innerObj = {};
                            innerObj[fullSubName] = subValue;
                            query += param(innerObj) + '&';
                        }
                    }
                    else if (value !== undefined && value !== null) {
                        query += encodeURIComponent(name) + '=' + encodeURIComponent(value) + '&';
                    }
                }
                return query.length ? query.substr(0, query.length - 1) : query;
            };
            return angular.isObject(data) && String(data) !== '[object File]' ? param(data) : data;
        }];
    }]);
    // Execute bootstrapping code and any dependencies.
    app.run(['$q', '$rootScope',
        function ($q, $rootScope) {
        }]);
})();

有关 angularjs $http.post 与 Ajax post 方法的更多信息,您可以查看make-angularjs-http-service-behave-like-jquery-ajax

重复上一步,再添加两个名为 mainCtrl.jsuserAccountService.js 的 JavaScript 文件。

第一个文件 mainCtrl.js 将是我们用于视图(网页)的控制器代码,代码如下:

(function () {
    'use strict';
    var controllerId = 'mainCtrl';
    angular.module('app').controller(controllerId,
        ['userAccountService', mainCtrl]);
    function mainCtrl(userAccountService) {
        // Using 'Controller As' syntax, so we assign this to the vm variable (for viewmodel).
        var vm = this;
        // Bindable properties and functions are placed on vm.
        vm.title = 'mainCtrl';
        vm.isRegistered = false;
        vm.isLoggedIn = false;
        vm.userData = {
            userName: "",
            password: "",
            confirmPassword : "",
        };
        vm.registerUser = registerUser;
        vm.loginUser = loginUser;
        vm.getValues = getValues;
        function registerUser() {
            userAccountService.registerUser(vm.userData).then(function (data) {
                vm.isRegistered = true;
            }, function (error, status) {
                vm.isRegistered = false;
                console.log(status);
            });
        }
        function loginUser() {
            userAccountService.loginUser(vm.userData).then(function (data) {
                vm.isLoggedIn = true;
                vm.userName = data.userName;
                vm.bearerToken = data.access_token;
            }, function (error, status) {
                vm.isLoggedIn = false;
                console.log(status);
            });
        }
        function getValues() {
            userAccountService.getValues().then(function (data) {
                vm.values = data;
                console.log('back... with success');
            });
        }
    }
})();

对于文件 userAccountService.js,它将是我们用于调用服务器 Web API 以注册和登录用户,以及登录后获取 ValuesController 数据的服务,代码如下:

(function () {
    'use strict';
    var serviceId = 'userAccountService';
    angular.module('app').factory(serviceId, ['$http', '$q', userAccountService]);
    function userAccountService($http, $q) {
        // Define the functions and properties to reveal.
        var service = {
            registerUser: registerUser,
            loginUser: loginUser,
            getValues: getValues,
        };
        var serverBaseUrl = "https://:60737";
        
        return service;
        var accessToken = "";
        function registerUser(userData) {
            var accountUrl = serverBaseUrl + "/api/Account/Register";
            var deferred = $q.defer();
            $http({
                method: 'POST',
                url: accountUrl,
                data: userData,
            }).success(function (data, status, headers, cfg) {
                console.log(data);
                deferred.resolve(data);
            }).error(function (err, status) {
                console.log(err);
                deferred.reject(status);
            });
            return deferred.promise;
        }
        function loginUser(userData) {
            var tokenUrl = serverBaseUrl + "/Token";
            if (!userData.grant_type) {
                userData.grant_type = "password";
            }
            var deferred = $q.defer();
            $http({
                method: 'POST',
                url: tokenUrl,
                data: userData,
            }).success(function (data, status, headers, cfg) {
                // save the access_token as this is required for each API call. 
                accessToken = data.access_token;
                // check the log screen to know currently back from the server when a user log in successfully.
                console.log(data);
                deferred.resolve(data);
            }).error(function (err, status) {
                console.log(err);
                deferred.reject(status);
            });
            return deferred.promise;
        }
        function getValues() {
            var url = serverBaseUrl +  "/api/values/";
            var deferred = $q.defer();
            $http({
                method: 'GET',
                url: url,
                headers: getHeaders(),
            }).success(function (data, status, headers, cfg) {
                console.log(data);
                deferred.resolve(data);
            }).error(function (err, status) {
                console.log(err);
                deferred.reject(status);
            });
            return deferred.promise;
        }
        // we have to include the Bearer token with each call to the Web API controllers. 
        function getHeaders() {
            if (accessToken) {
                return { "Authorization": "Bearer " + accessToken };
            }
        }
    }
})();

最后,我们需要添加包含 UI 页面的 html 文件,与之前一样,在解决方案资源管理器中,右键单击 AngularJsClient 项目。选择添加,然后选择HTML 页

为项指定名称对话框中,将文件命名为 Index.html。单击“确定”。

正如我提到的,我们的 html 将是原始的,所以类似这样的内容应该足够了:

    <xmp>
        <!DOCTYPE HTML>
        <html xmlns="http://www.w3.org/1999/xhtml">
        <head>
            <title></title>
        </head>
        <body>
            <div data-ng-app="app" data-ng-controller="mainCtrl as vm">
                <h3> Test Values Controller</h3>

                <div>
                    <span data-ng-show="vm.isRegistered">User Registered Successfully</span><br />
                    <span data-ng-show="vm.isLoggedIn">Hello : {{vm.userName}}</span><br />
                    <span data-ng-show="vm.isLoggedIn">Your Bearer Token is : {{vm.bearerToken}}</span>
                </div>
                <br />
                <div>
                    <input type="text" name="userName" placeholder="Name" data-ng-model="vm.userData.userName" />
                    <input name="password" placeholder="Password" data-ng-model="vm.userData.password" />
                    <input name="confirmPassword" placeholder="Password" data-ng-model="vm.userData.confirmPassword" />
                    <input type="button" id="registerUser" value="Register" data-ng-click="vm.registerUser()" />
                    <input type="button" id="logniUser" value="Login" data-ng-click="vm.loginUser()" />
                </div>

                <button id="getValues" data-ng-click="vm.getValues()">Get Values</button>

                <ul>
                    <li data-ng-repeat="v in vm.values">
                        <div>
                            <strong>{{v}}</strong>
                        </div>
                    </li>
                </ul>

            </div>
            <script src="Scripts/angular.js"></script>

            <script>

            </script>
            <script src="app.js"></script>
            <script src="mainCtrl.js"></script>
            <script src="userAccountService.js"></script>

        </body>
    </html>
</xmp>

在 Web API 中启用跨域资源共享 (CORS)

默认情况下,浏览器安全设置会阻止网页向另一个域发出 AJAX 请求。此限制称为同源策略,但是,我们可以通过在 Web API 服务器上启用 CORS 来允许跨域请求。

要启用 Web API 中的 CORS,请使用 Microsoft.AspNet.WebApi.Cors 包,该包可在 NuGet 上找到。

在 Visual Studio 中,从工具菜单中,选择库包管理器,然后选择程序包管理器控制台。在程序包管理器控制台中,键入以下命令:

Install-Package Microsoft.AspNet.WebApi.Cors -ProjectName WebApi2Service

这将把 CORS 包及其所有依赖项安装到 WebApi2Service 项目中。

在解决方案资源管理器中,展开 WebApi2Service 项目。打开 App_Start/WebApiConfig.cs 文件。将以下代码添加到 WebApiConfig.Register 方法中。

现在运行这两个项目(WebApi2Service 和 AngularJsClient),并尝试注册用户,输入用户名、密码和确认密码,然后单击注册按钮,您将注意到我们已成功注册用户。


现在尝试按登录按钮,您将惊喜地发现,您会收到一个与 CORS 未为 /Token 启用的相关错误!!!

但是我们已经启用了 Web API 的 CORS,那么我们错过了什么?

此错误的基本原因是 CORS 仅为 Web API 启用,而不是为客户端请求颁发令牌的令牌中间件提供程序启用。因此,要解决此问题,我们需要在提供程序颁发令牌时添加“Access-Control-Allow-Origin”标头,因此:

在解决方案资源管理器中,展开 WebApi2Service 项目。打开 Providers/ApplicationOAuthProvider.cs 文件,并将此行代码添加到 ApplicationOAuthProvider.GrantResourceOwnerCredentials 方法中。

现在再次尝试,并登录您之前注册的用户,登录应该会成功,您可以看到我们在登录后从服务器获得的令牌。

最后,单击 GetValues 按钮,您也应该能够成功从 Values Controller 获取值。

摘要

在本教程中,我们解决了使用来自 AngularJs 客户端的 Web API 2 bearer 令牌功能时遇到的大部分问题,希望它能帮助您更多地了解 Web API 2 和 AngularJs。

© . All rights reserved.