尝试新的开发组合:Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS之配置IdentityServer Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS之数据迁移 Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS之添加实体 Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS之显示登录视图
在上一篇文章,完成了实体的定义,接下来要做的是定义应用服务,以便提供api接口。不过,为了更好的结合客户端,笔者决定暂时把这个工作缓一下,和客户端的改造一同进行。
新建一个名为SimpleCmsWithAbp-Client的文件夹,把SimpleCMS的Ext JS脚本复制到文件夹中。然后用Visual Studio Code打开该文件夹。使用Visual Studio Code是因为在这里不需要编写C#代码,使用vscode比使用Visual Studio方便很多。
打开文件夹后,先为vscode安装一个名为IIS Express的扩展(warren-buckley.iis-express)用来启动IIS Express。扩展安装完成后,就可在打开的文件中单击鼠标右键,在菜单中选择命令面板,就可在如下图所示的输入框中执行以下命令来启动、重新启动或停止IIS Express了:
IIS Express: Start Website - 启动IIS Express IIS Express: Stop Website - 停止IIS Express IIS Express: Restart Website - 重新启动IIS Express
由于ABP框架模版默认是使用4200端口来作为跨域访问的客户端网站端口的,因而,我们需要将当前文件夹的访问端口设置为4200。在当前文件夹下新建一个名为.vscode的文件夹,然后在文件夹下创建一个名为iisexpress.json的文件,并在里面添加以下代码:
{ "port":4200 }
好了,现在可以启动IIS Express来访问网站了,不过执行肯定是错误的。我们先打开app.json文件,将里面的首页文件(indexHtmlPath)设置回index.html。然后打开index.html文件,在调用bootstrap.js脚本的SCRIPT标记前添加以下代码:
<script> var ROOTPATH = 'https://localhost:21021'; var LOCALPATH = 'https://localhost:4200'; </script>
以上代码用于指定远程访问地址和本地地址。
接下来打开app\util\Url.js,将getResource方法修改为以下代码:
getResource: function (res) { var me = this; return LOCALPATH + '/' + me.resources[res]; }
代码完成后按CTRL+打开终端窗口执行一次sencha app build`,重建生成一次项目。现在在浏览器访问应用,会正常显示界面了,不过会提示访问错误。问题不大,接下来就是要修正这些问题。
WebApi是通过令牌认证(Token Auth)来访问的,需要在Ajax的头部添加Authorization来发送认证令牌。在修改Ajax的头部前,需要先考虑这令牌如何保存的问题。根据《Where to Store your JWTs – Cookies vs HTML5 Web Storage》一文,建议的方法是将令牌保存在Cookies中,但这又带来一个CORS的安全问题,需要为Ajax开启cors保护。
在研究了ABP框架模版自带的angular版本客户端的启动过程(AppPreBootstrap.ts),会发现在提交请求时,会在Ajax的头部添加Authorization、.AspNetCore.Culture和Abp.TenantId这三个头。为了便于在Ajax请求添加这三个头,我们可在app\util文件夹下,新建一个名为Headers.js的脚本文件,然后添加以下代码:
Ext.define('SimpleCMS.util.Headers',{ alternateClassName: 'HEADERS', singleton: true, requires:[ 'Ext.util.Cookies' ], authTokenCookieName : 'Abp.AuthToken', authTokenHeaderName : 'Authorization', tenantIdCookieName : 'Abp.TenantId', tenantIdHeaderName: 'Abp.TenantId', cultureTCookieName: 'Abp.Localization.CultureName', cultureHeaderName: '.AspNetCore.Culture', getAuthToken: function(){ return Ext.util.Cookies.get(this.authTokenCookieName); }, getTenantId: function(){ return Ext.util.Cookies.get(this.tenantIdCookieName); }, getCulture: function(){ return Ext.util.Cookies.get(this.cultureTCookieName); }, setCookies: function(name, value , expires , path , domain , secure ){ Ext.util.Cookies.set(name, value , expires , path , domain , secure); }, getHeaders: function(){ var me = this; return { 'Authorization' : 'Bearer ' + me.getAuthToken(), 'Abp.TenantId' : me.getTenantId(), '.AspNetCore.Culture' : me.getCulture() } } });
方法getHeaders用户返回组合好的头部,还有3个get方法用于从Cookies中返回令牌、当前用户的默认语言和组合编号,setCookies方法则用于存储Cookies。添加Header类后,在app.js中添加对它的引用。
为了方便的将请求头自动写入Ajax请求的头部,而不需要每次写请求时才加入,最好的方式是重写Ext.Ajax类或在发送请求前统一处理。由于Ext.Ajax是单例模式的类,不便于重写,因而只有在发送请求前统一处理了。经过查看API,发现Ext.Ajax有beforerequest事件,正是所需的。接下来要考虑的是在哪里执行了。经过测试,发现在Application.js的launch方法是不行,因为这时候主视图已经初始化并开始执行流程了。通过查阅Ext.app.Application的API,发现还有一个init方法,它会在主视图初始化之前执行,正是所需的。在init方法中添加以下代码:
init: function(){ Ext.util.Format.defaultValue = function (value, defaultValue) { return Ext.isEmpty(value) ? defaultValue : value; } Ext.Ajax.on('beforerequest', this.onAjaxBeforeRequest, this); Ext.Ajax.cors = true; },
在代码中,通过on方法将beforerequest事件绑定到了onAjaxBeforeRequest方法。将cors设置为true是打开Ajax的CORS防御机制。
方法onAjaxBeforeRequest的代码如下:
onAjaxBeforeRequest: function(conn , options , eOpts ){ if(!options.headers) options.headers = {} Ext.apply( options.headers, HEADERS.getHeaders()); options.jsonData = true; }
代码先判断Ajax的选项是否已包含headers的配置,如果没有,则将headers设置为一个对象,然后调用getHeaders方法将返回的头部信息通过apply方法复制到headers中。将jsonData设置为true是为了将提交的contentType设置为application/json,而不是默认的text/plain。
由于使用的是令牌认证,因而,只要验证一下Cookies中是否存在令牌,就可知道用户是否已经登录,如果没有,则显示登录页,如果有,则去获取用户信息。打开app\view\main\MainController.js文件,在onMainViewRender方法中,将代码修改为以下代码:
onMainViewRender: function () { var me = this, token = HEADERS.getAuthToken(); if (Ext.isEmpty(token)) { me.setCurrentView("login"); return; } },
好了,现在可以显示登录视图了。总的感觉来说,这比原来的方式简单不少。