返回总目录:ABP+AdminLTE+Bootstrap Table权限管理系统一期
简介
经过前几节,我们已经解决数据库,模型,DTO,控制器和注入等问题.那么再来看一下登录逻辑.这里算是前面几节的一个初次试水.
首先我们数据库已经有的相应的数据.
添加Login方法
模型和DTO已经建好,所以我们直接在服务层添加Login方法就可以了.
注入IUserService接口
在展现层添加Account控制器,注入IUserService接口,调用Login方法.
添加视图页面.
然后添加视图页面.
效果
运行一下,看一下结果.
注意事项
除了页面比较漂亮(哈哈),这些本来都没有什么好说的,直接上图,
这里值得注意的是,我们在创建下面的方法,在调用接口的的时候会报一个错误:web的App_Data/Logs/Logs.txt日志文件中查看到打印到错误信息。
public async Task<ListResultDto<UserInfoDto>> GetUsers() { var users = await _userRepository.GetAllListAsync(); return new ListResultDto<UserInfoDto>( users.MapTo<List<UserInfoDto>>() ); }
错误提示: Mapper not initialized. Call Initialize with appropriate configuration. If you are trying to use mapper instances through a container or otherwise, make sure you do not have any calls to the static Mapper.Map methods, and if you're using ProjectTo or UseAsDataSource extension methods, make sure you pass in the appropriate IConfigurationProvider instance.
意思是:映射器未初始化。 通过适当的配置调用初始化。 如果您尝试通过容器或其他方式使用映射器实例,请确保您没有任何调用静态Mapper.Map方法,如果您使用ProjectTo或UseAsDataSource扩展方法,请确保您传入相应的IConfigurationProvider实例。
原因:Mapper not initialized 映射未初始化
解决:在ApplicationModule类中增加依赖typeof(AbpAutoMapperModule)即可。
using System.Reflection; using Abp.Modules; using Abp.AutoMapper; namespace JCmsErp { [DependsOn(typeof(JCmsErpCoreModule), typeof(AbpAutoMapperModule))] public class JCmsErpApplicationModule : AbpModule { public override void Initialize() { IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly()); } } }
另外上面这个提示窗是不是很漂亮,登录页面的设计样式都是自己写的,自己这个弹窗,只是abp自己封装.我们来看一下具体的代码.
abp.message.confirm( '请输入密码.', //确认提示 '确定?',//确认提示(可选参数) function (isConfirmed) { if (isConfirmed) { //...delete user 点击确认后执行 } } );
其实abp封装很多js函数库,这让我不得不感叹土牛(土耳其牛人)真是牛啊!看下图左边部分js文件
包括像abp.jquery.js,ABP服务端支持标准的ajax的请求/输出。建议大家使用abp.jquery.js中提供的ajax请求方法,这个方法基于jquery的ajax方法,可以自动处理服务端的异常信息,当然,如果你对js很熟练的话,也可以根据自己的需要写ajax。你也可以使用jquery的ajax方法调用,但是需要设置一下默认请求参数,dataType 设置为 'json', type 设置为 'POST' and contentType 设置为 'application/json,在发送请求时需要将js对象转换成json字符串,和$.ajax一样,你也可以传递参数覆盖abp.ajax的默认参数abp.ajax返回一个promise类型.Login上标记了 HttpPost 特性 abp.ajax默认以 POST 方式请求. 返回值被简化成了一个匿名对象。
abp封装的Javascript函数库
消息
用于向用户显示对话框,展示消息或者得到用户的确认,ABP默认采用的sweetalert库实现的对话框信息,使用时你需要引用sweetalert的样式和js,在我的登录页面里面已经引用了,并且引用abp.sweet-alert.js就可以使用下列API了:
abp.message.info('some info message', 'some optional title'); abp.message.success('some success message', 'some optional title'); abp.message.warn('some warning message', 'some optional title'); abp.message.error('some error message', 'some optional title'); abp.message.confirm( 'User admin will be deleted.', //确认提示 'Are you sure?',//确认提示(可选参数) function (isConfirmed) { if (isConfirmed) { //...delete user 点击确认后执行 } } );
用户界面的繁忙提示
设置一个半透明层,阻止点击页面元素,可以覆盖局部或者整个页面,例子如下:
abp.ui.block(); //覆盖整个页面 abp.ui.block($('#MyDivElement')); //覆盖指定元素,可以把jquery对象作为参数 abp.ui.block('#MyDivElement'); //或者直接使用选择器参数 abp.ui.unblock(); //整个页面解除覆盖 abp.ui.unblock('#MyDivElement'); //指定元素解除覆盖
UI Block API使用blockUI这个js库来实现效果的,如果使用这个api需要在页面引用blockUI的js库和abp.blockUI.js文件。
UI Busy API 指示页面繁忙的API,如ajax请求中:
abp.ui.block = function (elm) { if (!elm) { $.blockUI(); } else { $(elm).block(); } }; abp.ui.unblock = function (elm) { if (!elm) { $.unblockUI(); } else { $(elm).unblock(); } };
abp.ui.setBusy('#MyLoginForm');
abp.ui.clearBusy('#MyLoginForm');
Js日志接口
这个主要是对浏览器console.log('...') 进行的包装,可以支持所有浏览器,你可以通过设置abp.log.level来控制日志输出,和服务端一样,如设置了abp.log.levels为INFO时就不会输出debug日志了,你也可以根据你的需要定制重新这些API。
abp.notify.success = function (message, title, options) { abp.log.warn('abp.notify.success is not implemented!'); }; abp.notify.info = function (message, title, options) { abp.log.warn('abp.notify.info is not implemented!'); }; abp.notify.warn = function (message, title, options) { abp.log.warn('abp.notify.warn is not implemented!'); }; abp.notify.error = function (message, title, options) { abp.log.warn('abp.notify.error is not implemented!'); };
格式化字符串(abp.utils.formatString)
和C#的string.Format一样的用法
/* Formats a string just like string.format in C#. * Example: * abp.utils.formatString('Hello {0}','Tuana') = 'Hello Tuana' ************************************************************/ abp.utils.formatString = function () { if (arguments.length < 1) { return null; } var str = arguments[0]; for (var i = 1; i < arguments.length; i++) { var placeHolder = '{' + (i - 1) + '}'; str = abp.utils.replaceAll(str, placeHolder, arguments[i]); } return str; };
最后,当然我们登录页面也可以改一下.这样就更简洁了.
var url = "/Account/Login"; //构建要传输的参数对象 var newPerson = { userName:$("#userName").val(), password:$("#password").val() } }; //调用abp的ajax方法 abp.ajax({ url:url, data: JSON.stringify(newPerson) //转换成json字符串 }).done(function (data) { abp.message.warn('用户名或密码错误!', '登录失败'); });
至此,我们登录逻辑,和JavaScript封装模块就全部完成了,其实abp提示窗还蛮好看的,大家也可以借鉴一下在自己的项目里面.
AJAX操作问题
现代的应用经常会使用AJAX,尤其是单页应用,几乎是和服务器通信的唯一手段,执行AJAX通常会有以下步骤:
-
基本上:为了执行一个AJAX调用,首先你要在客户端提供一个可供请求的URL,选取提交数据和一个方法(GET,POST,PUT,DELETE)。
-
等待调用完成后,处理返回结果。当执行AJAX调用服务器端的时候,可能会有错误(一般是网络错误)。当然也有可能是服务器端产生了一些错误,对于这些错误会,服务器会返回一个失败的响应并且附上错误消息给客户端。
-
客户端代码应该处理这些错误,并且可以选择通知用户(可以显示一个错误对话框)。如果没有错误且服务器端返回了数据,客户端必须处理它。还有你应该限制页面的某个区域(或者整个页面),并显示一个忙碌的指示直到AJAX操作完成。
-
服务器端在得到请求后执行服务器端代码,捕获异常并返回一个有效的响应给客户端。在错误情况下,可以选择发送错误消息给客户端。如果是验证错误,服务器端可以添加验证错误的验证信息。在成功情况下,可以发送返回值给客户端。
ABP的方式
由于使用 abp.ajax 函数对AJAX调用进行了封装, 所以ABP能自动化这些步骤。下面是一个AJAX调用示例:
var newPerson = { name: 'Dougles Adams', age: 42 }; abp.ajax({ url: '/People/SavePerson', data: JSON.stringify(newPerson) }).done(function(data) { abp.notify.success('created new person with id = ' + data.personId); });
abp.ajax得到 options 作为对象。你可以传递任何有效的jQuery的 $.ajax 函数中的参数。有一些默认参数:dataType 是 json,type是 POST,还有 contentType是 application/json(在发送数据到服务器端之前,我们需要调用 JSON.stringify 将脚本对象转换为JSON字符串)。通过对apb.ajax传递options可以覆盖默认值。
abp.ajax返回promise。因此,你可以写这些处理函数:done,fail,then等等。在这个例子中,我们对 PeopleController's SavePerson action 发送了一个简单的AJAX请求。在 done 处理函数中,我们对新创建的person取得了它的主键id并且显示了创建成功的通知。让我们看看 MVC Controller:
public class PeopleController : AbpController { [HttpPost] public JsonResult SavePerson(SavePersonModel person) { //TODO: 保存新创建的person到数据库并且返回person的id return Json(new {PersonId = 42}); } }
正如你猜测的 SavePersonModel 包含了Name和Age属性。SavePerson 被标记为 HttpPost 特性,因为abp.ajax默认方法是POST。通过返回了匿名对象简化了方法实现。
这个看上去很简单直白,但是ABP在背后做了很多重要的处理。让我们深入了解一下:
AJAX 返回消息
即使我们直接的返回了一个带有PersonId = 2 的对象,ABP也会使用 MvcAjaxResponse 对象来包装它。事实上AJAX响应返回的内容应该像下面一样:
{ "success": true, "result": { "personId": 42 }, "error": null, "targetUrl": null, "unAuthorizedRequest": false, "__abp": true }
在这里所有的属性都是驼峰命名的(因为这在JavaScript中是惯例),即使在服务端代码中是PascalCased的。下面解释一下所有的字段:
-
success:boolean类型的值(true或者false),用来表示操作的成功状态。如果是ture,abp.ajax会解析该promise并且调用 done 函数。如果是false(在方法被调用的时候,如果有个异常被抛出),它会调用 fail 函数并且使用 abp.message.error 函数显示 error 消息。
-
result:控制器的action的实际返回值。如果success是ture并且服务器发送了返回值那么它才是有效的。
-
error:如果success是false,这个字段是一个包含 message和details 字段的对象。
-
targetUrl:如果需要的话,这提供了一种可能性:服务器端发送一个URL到客户端,使客户端可以重定向到其它的URL。
-
unAuthorizedRequest:这提供了一种可能性:服务器端发送通知给客户端该操作未被授权,或者是未认证用户。如果该值是true,那么abp.ajax会 reloads 当前的页面。
-
__abp:通过ABP包装响应返回的特殊签名。你自己不需要用到它,但是abp.ajax会处理它。
这种格式的对象会被 abp.ajax 函数识别且处理。abp.ajax会得到控制器的实际返回值(一个带有personid属性的对象),如果没有错误的话,那么你会在done函数中处理返回值。
处理错误
正如上面所述,ABP在服务器端处理异常,并且返回一个带有错误消息的对象。如下所示:
{ "targetUrl": null, "result": null, "success": false, "error": { "message": "An internal error occured during your request!", "details": "..." }, "unAuthorizedRequest": false, "__abp": true }
正如你看到的,success是false 并且 result是null。abp.ajax处理这个对象,并且使用abp.message.error函数来显示错误消息给用户。如果你的服务器端代码抛出了 UserFriendlyException 类型的异常。它会直接的显示异常信息给用户。否则,它会隐藏实际的错误(将错误写入日志),并且显示一个标准的“服务器内部错误...”信息给用户。所有的这些都是ABP自动处理的。
你可能想为某个特别的AJAX调用禁止显示消息,那么添加 ** abpHandleError: false** 到 abp.ajax的options。
HTTP状态码
在异常发生的时候,ABP会返回给定的HTTP状态码:
-
401:未经身份验证的请求(没有登录,但是服务器端需要身份验证);
-
403:未授权的请求;
-
500:所有其它类型的异常。
WrapResult和DontWrapResult特性
使用 WrapResult和DontWrapResult 特性,可以对控制器的某个action或者所有的action来控制包装。
ASP.NET MVC 控制器
如果返回的类型是 JsonResult(或者Task<JsonResult>),那么ABP会默认包装ASP.NET MVC action的返回结果。你可以使用 WrapResult 特性来改变它。如下所示:
public class PeopleController : AbpController { [HttpPost] [WrapResult(WrapOnSuccess = false, WrapOnError = false)] public JsonResult SavePerson(SavePersonModel person) { //TODO: 保存新创建的person到数据库并且返回person的id return Json(new {PersonId = 42}); } }
作为一个快速开发方式,我们只能使用 [DontWrapResult] 特性在这个相同的示例上。
你可以在启动配置里面改变这个默认的行为(使用 Configuration.Modules.AbpMvc()...)。
ASP.NET Web API 控制器
如果action被成功执行,ABP 不会默认包装 Web API Action的返回结果。如果需要的话,你可以添加WrapResult特性到action或者控制器上。但是它会 包装异常。
你可以在启动配置里面改变这个默认的行为(使用 Configuration.Modules.AbpWebApi()...)。
动态Web API层
默认 ABP会 包装 动态Web API层的所有方法。你可以在你应用服务的接口上使用 WrapResult和DontWrapResult 特性来改变这个行为。
你可以在启动配置里面改变这个默认的行为(使用 Configuration.Modules.AbpWebApi()...)。
ASP.NET Core 控制器
ABP会自动包装JsonResult,ObjectRes以及那些没有实现IActionResult对象。详情请查阅ASP.NET Core文档。
你可以在启动配置里面改变这个默认的行为(使用 using Configuration.Modules.AbpAspNetCore()...)。
动态Web API层
虽然ABP提供了一种调用Ajax的简单机制,但是在真实世界的应用中,为每个Ajax调用编写javascript函数是很经典的。例如:
//创建一个抽象了Ajax调用的function var savePerson = function(person) { return abp.ajax({ url: '/People/SavePerson', data: JSON.stringify(person) }); }; //创建一个新的 person var newPerson = { name: 'Dougles Adams', age: 42 }; //保存该person savePerson(newPerson).done(function(data) { abp.notify.success('created new person with id = ' + data.personId); })
;
这是一个最佳实践,但是对每个AJAX调用函数都这样做,那是耗时且乏味的。对于应用服务和控制器,ABP能够自动的生成这些函数。
详情请阅读动态Web API层文档和ASP.NET Core文档。
Javascript Notification API
当一些事情发生的时候,我们喜欢显示一些别致的能够自动消失的通知,例如,当某个记录被保存或者某个问题发生的时候。ABP定义了标准的API实现了该功能。
abp.notify.success('a message text', 'optional title'); abp.notify.info('a message text', 'optional title'); abp.notify.warn('a message text', 'optional title'); abp.notify.error('a message text', 'optional title');
作为通知库的 自定义选项,它也能够取得第3个参数(对象)。
通知API默认是使用toastr库实现的。要使toastr生效,你应该引用toastr的css和javascript文件,然后再在页面中包含abp.toastr.js作为适配器。一个toastr成功通知如下所示:
![](https://i-blog.csdnimg.cn/blog_migrate/2517047f7c834595f23c10bcf93aa88e.png)
你也可以用你最喜欢的通知库实现通知。只需要在自定义javascript文件中重写所有的函数,然后把它添加到页面中而不是abp.toastr.js中(你可以检查该文件看它是否实现,这个相当简单)。
abp.message简介
消息API被用来向用户显示一个消息或者从用户那里得到一个确认。
消息API默认实现方式是使用了sweetalert库。使用时你需要引用sweetalert的样式和js,然后把 abp.sweet-alert.js 作为适配器包含到你的页面中。
显示消息
如下所示:
abp.message.info('some info message', 'some optional title');
abp.message.success('some success message', 'some optional title'); abp.message.warn('some warning message', 'some optional title'); abp.message.error('some error message', 'some optional title');
成功的消息框显示如下:
![](https://i-blog.csdnimg.cn/blog_migrate/9f1a60145a7bc36a7a5dbd4736291148.png)
Confirmation对话框
如下所示:
abp.message.confirm(
'User admin will be deleted.', //确认提示
'Are you sure?',//确认提示(可选参数) function (isConfirmed) { if (isConfirmed) { //...delete user 点击确认后执行 } } );
第二个参数(标题)是可选的(所以,回调函数可以作为第二个参数)。
确认消息框显示如下:
![](https://i-blog.csdnimg.cn/blog_migrate/9cb386c2646216f90e3a7a5ec887399a.png)
ABP在内部使用了消息API,例如:如果某个AJAX调用失败,那么它会调用abp.message.error。
Javascript UI Block & Busy API
ABP提供了有用的API,使整个页面或者页面的某个部分被遮罩层覆盖实现阻塞或者繁忙指示(使用加载图标表示繁忙)。
UI Block API
这个API使用一个透明的遮罩层(透明度可调节)来遮住整个页面或者该页面的某个元素。因此用户不能够点击。当你保存表单或者加载某个区域时(某个层或者整个页面),这是相当有用的。
如下所示:
abp.ui.block(); //遮住整个页面
abp.ui.block($('#MyDivElement')); //遮罩某个元素,在这里可以使用jQuery选择器选择元素..
abp.ui.block('#MyDivElement'); //..或者直接指定元素 abp.ui.unblock(); //解除遮罩 abp.ui.unblock('#MyDivElement'); //对指定元素解除遮罩
UI Block API 默认是使用jQuery插件block UI来实现的。为了能正常运行,你需要引用脚本文件,然后包含 abp.blockUI.js 文件作为适配器到你的页面中。
UI Busy API
该API被用来指示某些页面或者元素正在忙碌(加载)。例如:当你提交表单数据到服务器的时候,你可能想要遮罩这个表单并显示一个忙碌的指示器。
如下所示:
abp.ui.setBusy('#MyLoginForm');
abp.ui.clearBusy('#MyLoginForm');
![](https://i-blog.csdnimg.cn/blog_migrate/2508c6dd36757b1ce0b151cc008fefc7.png)
参数应该是一个jQuery选择器(如:#MyLoginForm)或者jQuery对象(如:$('#MyLoginForm'))。为了使整个页面都是在繁忙状态,你应该传递null或者body作为选择器。
setBusy函数能够传入一个promise(作为第二个参数)并且自动的清除busy,当该promise完成的时候。如下所示:
abp.ui.setBusy(
$('#MyLoginForm'),
abp.ajax({ ... })
);
由于abp.ajax返回的是promise,所以我们能直接使用它作为参数。如果你想了解更多关于promise的资料,请查阅jQuery的Deferred。setBusy对Q提供支持(以及angulars的$http服务)。
UI Busy API 使用spin.js实现的。为使其正常运行,你应该引用该脚本文件,然后在你的页面中包含 abp.spin.js 作为适配器。
ABP表现层 - 事件总线EventBUS
简介
Pub/Sub 事件模型被广泛的应用在客户端。ABP包含了一个 简单的全局事件总线 用来注册事件并且触发事件。
注册事件
你可以使用 abp.event.on 来 注册 一个 全局事件 。示例如下:
abp.event.on('itemAddedToBasket', function (item) { console.log(item.name + ' is added to basket!'); });
第一个参数是 该事件的唯一名称。另一个参数是 回调函数,当指定的事件被触发后将调用该参数。
你可以使用 abp.event.off 方法来 卸载 已注册的事件。
注意:为了能够卸载指定的事件,应该提供相同的事件函数。
正如上面的示例所展示的,你应该将回调函数设置为一个变量,然后在 on和off 中使用它。
触发事件
abp.event.trigger 被用来触发全局事件。触发一个已注册的事件的代码如下:
abp.event.trigger('itemAddedToBasket', {
id: 42,
name: 'Acme Light MousePad'
});
第一个参数是 该事件的唯一名称。第二个是(可选的)事件参数。你可以添加任何数量的参数,并且在回调方法中获得它们。
ABP表现层 - Javascript 日志 API
简介
当你想要在客户端写一些简单的日志的时候,你可以使用 console.log('...') API。但是,它不是所有的浏览器都支持该API,并且该函数也可能破坏你的脚本。所以,在使用的时候你首先应该检查console是否有效。还有,你可能想在其它地方写日志。甚至你可能对写日志的等级也有要求。ABP定义了安全的日志函数:
abp.log.debug('...');
abp.log.info('...');
abp.log.warn('...');
abp.log.error('...'); abp.log.fatal('...');
你可以通过设置 abp.log.level 对 abp.log.levels 中的某个日志等级进行更改(例如:abp.log.levels.INFO 不会记录调试日志)。这些函数默认将日志记录到了浏览器的控制台里了。但如果你需要的话,你也可以重写或者扩展这个行为。
ABP表现层 - 其他工具函数OtherUtilities
ABP提供了一些通用的工具函数。
abp.utils.createNamespace
用于创建更深的命名空间。假设我们有一个基命名空间 abp,然后想要创建或者获得 abp.utils.strings.formatting 命名空间。不需要像下面这样写:
//创建或获得namespace
abp.utils = abp.utils || {};
abp.utils.strings = abp.utils.strings || {};
abp.utils.strings.formatting = abp.utils.strings.formatting || {};
//给该namespace添加一个function
abp.utils.strings.formatting.format = function() { ... };
我们可以这样写:
var formatting = abp.utils.createNamespace(abp, 'utils.strings.formatting');
//给该namespace添加一个function
formatting.format = function() { ... };
这样即安全又简单的创建了更深层次的命名空间。注意,第一个参数是必须存在的根命名空间。
abp.utils.formatString
近似于C#中的string.Format()方法。示例如下:
var str = abp.utils.formatString('Hello {0}!', 'World'); //str = 'Hello World!' var str = abp.utils.formatString('{0} number is {1}.', 'Secret', 42); //str = 'Secret number is 42'