返回总目录:ABP+AdminLTE+Bootstrap Table权限管理系统一期

上一节我们讲到登录逻辑,我做的登录逻辑很简单的,我们来看一下abp module-zero里面的登录代码.

 #region Login / Logout

        public ActionResult Login(string returnUrl = "")
{
if (string.IsNullOrWhiteSpace(returnUrl))
{
returnUrl = Request.ApplicationPath;
} return View(
new LoginFormViewModel
{
ReturnUrl = returnUrl,
IsMultiTenancyEnabled = _multiTenancyConfig.IsEnabled
});
} [HttpPost]
public async Task<JsonResult> Login(LoginViewModel loginModel, string returnUrl = "", string returnUrlHash = "")
{
CheckModelState(); var loginResult = await GetLoginResultAsync(
loginModel.UsernameOrEmailAddress,
loginModel.Password,
loginModel.TenancyName
); await SignInAsync(loginResult.User, loginResult.Identity, loginModel.RememberMe); if (string.IsNullOrWhiteSpace(returnUrl))
{
returnUrl = Request.ApplicationPath;
} if (!string.IsNullOrWhiteSpace(returnUrlHash))
{
returnUrl = returnUrl + returnUrlHash;
} return Json(new AjaxResponse { TargetUrl = returnUrl });
} private async Task<AbpLoginResult<Tenant, User>> GetLoginResultAsync(string usernameOrEmailAddress, string password, string tenancyName)
{
try
{ var loginResult = await _logInManager.LoginAsync(usernameOrEmailAddress, password, tenancyName); switch (loginResult.Result)
{
case AbpLoginResultType.Success:
return loginResult;
default:
throw CreateExceptionForFailedLoginAttempt(loginResult.Result, usernameOrEmailAddress, tenancyName);
}
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
} } private async Task SignInAsync(User user, ClaimsIdentity identity = null, bool rememberMe = false)
{
if (identity == null)
{
identity = await _userManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
} AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = rememberMe }, identity);
} private Exception CreateExceptionForFailedLoginAttempt(AbpLoginResultType result, string usernameOrEmailAddress, string tenancyName)
{
switch (result)
{
case AbpLoginResultType.Success:
return new ApplicationException("Don't call this method with a success result!");
case AbpLoginResultType.InvalidUserNameOrEmailAddress:
case AbpLoginResultType.InvalidPassword:
return new UserFriendlyException(L("LoginFailed"), L("InvalidUserNameOrPassword"));
case AbpLoginResultType.InvalidTenancyName:
return new UserFriendlyException(L("LoginFailed"), L("ThereIsNoTenantDefinedWithName{0}", tenancyName));
case AbpLoginResultType.TenantIsNotActive:
return new UserFriendlyException(L("LoginFailed"), L("TenantIsNotActive", tenancyName));
case AbpLoginResultType.UserIsNotActive:
return new UserFriendlyException(L("LoginFailed"), L("UserIsNotActiveAndCanNotLogin", usernameOrEmailAddress));
case AbpLoginResultType.UserEmailIsNotConfirmed:
return new UserFriendlyException(L("LoginFailed"), "UserEmailIsNotConfirmedAndCanNotLogin");
case AbpLoginResultType.LockedOut:
return new UserFriendlyException(L("LoginFailed"), L("UserLockedOutMessage"));
default: //Can not fall to default actually. But other result types can be added in the future and we may forget to handle it
Logger.Warn("Unhandled login fail reason: " + result);
return new UserFriendlyException(L("LoginFailed"));
}
} public ActionResult Logout()
{
AuthenticationManager.SignOut();
return RedirectToAction("Login");
} #endregion

由于abp涉及到租户和身份验证的问题,所以登录有点繁琐.分析发现主要包括以下几个步骤:

1、 GetLoginResultAsync --> loginManager.LoginAsync --> userManager.CreateIdentityAsync:不要以为调用了LoginAsync就以为是登录,其实这是伪登录。主要根据用户名密码去核对用户信息,构造User对象返回,然后再根据User对象的身份信息去构造身份证(CliamsIdentity)。
2、SignInAsync --> AuthenticationManager.SignOut
-->AuthenticationManager.SignIn

AuthenticationManager(认证管理员),负责真正的登入登出。SignIn的时候将第一步构造的身份证(CliamsIdentity)交给证件所有者(ClaimsPrincipal)。   
       登录完成之后,我们通常会有一个记住用户名密码的功能,有人就会想到abp中的AbpSession.单其实AbpSession不是单纯意义上的Session,比如AbpSession里面的Userid就是通过以下方式获得的.
((ClaimsPrincipal)Thread.CurrentPrincipal).Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);

需要获取会话信息则必须实现IAbpSession接口。虽然你可以用自己的方式去实现它(IAbpSession),但是它在module-zero项目中已经有了完整的实现。IAbpSession包含还有其他信息.

 //
// 摘要:
// Defines some session information that can be useful for applications.
public interface IAbpSession
{
//
// 摘要:
// TenantId of the impersonator. This is filled if a user with Abp.Runtime.Session.IAbpSession.ImpersonatorUserId
// performing actions behalf of the Abp.Runtime.Session.IAbpSession.UserId.
int? ImpersonatorTenantId { get; }
//
// 摘要:
// UserId of the impersonator. This is filled if a user is performing actions behalf
// of the Abp.Runtime.Session.IAbpSession.UserId.
long? ImpersonatorUserId { get; }
//
// 摘要:
// Gets current multi-tenancy side.
MultiTenancySides MultiTenancySide { get; }
//
// 摘要:
// Gets current TenantId or null. This TenantId should be the TenantId of the Abp.Runtime.Session.IAbpSession.UserId.
// It can be null if given Abp.Runtime.Session.IAbpSession.UserId is a host user
// or no user logged in.
int? TenantId { get; }
//
// 摘要:
// Gets current UserId or null. It can be null if no user logged in.
long? UserId { get; } //
// 摘要:
// Used to change Abp.Runtime.Session.IAbpSession.TenantId and Abp.Runtime.Session.IAbpSession.UserId
// for a limited scope.
//
// 参数:
// tenantId:
//
// userId:
IDisposable Use(int? tenantId, long? userId);

AbpSession定义的一些关键属性:

1.UserId: 当前用户的标识ID,如果没有当前用户则为null.如果需要授权访问则它不可能为空。

2.TenantId: 当前租户的标识ID,如果没有当前租户则为null。

3.MultiTenancySide: 可能是Host或Tenant。

UserId和TenantId是可以为null的。当然也提供了不为空时获取数据的 GetUserId()和GetTenantId() 方法 。当你确定有当前用户时,你可以使用GetUserId()方法。如果当前用户为空,使用该方法则会抛出一个异常。GetTenantId()的使用方式和GetUserId()类似。

IAbpSession通常是以属性注入的方式存在于需要它的类中,不需要获取会话信息的类中则不需要它。如果我们使用属性注入方式,我们可以用 
NullAbpSession.Instance作为默认值来初始化它(IAbpSession)

    public IAbpSession AbpSession { get; set; }
private readonly IUserService _iUsersService;
public AccountController(IUserService iUsersService)
{
_iUsersService = iUsersService;
AbpSession = NullAbpSession.Instance;
} // GET: Account
public ActionResult Index()
{
var currentUserId = AbpSession.UserId;
return View();
}

由于授权是应用层的任务,因此我们应该在应用层和应用层的上一层使用IAbpSession(我们不在领域层使用IAbpSession是很正常的)。

ApplicationServiceAbpController 和 AbpApiController 这3个基类已经注入了AbpSession属性,因此在Application Service的实例方法中,能直接使用AbpSession属性。

ABP框架中的AbpSession, 并没有使用到System.Web.HttpSessionStateBase, 而是自己定义了一个Abp.Runtime.Session.IAbpSession接口, 并在Zero模块中通过AspNet.Identity组件实现了AbpSession对象的存值、取值。 所以即使Web服务重启,也不会丢失Session状态。在我们自己的项目中, Session对象只有UserId、TenantId、MultiTenancySide这几个属性是不够用的,可以自己扩充了几个属性和方法,使用起来非常方便。

首先我们定义IAbpSession扩展类获取扩展属性,通过扩展类,我们不需要做其他额外的更改,即可通过ApplicationService, AbpController 和 AbpApiController 这3个基类已经注入的AbpSession属性调用GetUserName()来获取扩展的Name属性。

接口代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace JCmsErp.AbpSessionExtension
{
public interface IAbpSessionExtension
{
string UserName { get; }
}
}

实现代码:

using Abp.Configuration.Startup;
using Abp.MultiTenancy;
using Abp.Runtime;
using Abp.Runtime.Session;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks; namespace JCmsErp.AbpSessionExtension
{
public class AbpSessionExtension : ClaimsAbpSession, IAbpSessionExtension
{
public AbpSessionExtension(IPrincipalAccessor principalAccessor, IMultiTenancyConfig multiTenancy, ITenantResolver tenantResolver, IAmbientScopeProvider<SessionOverride> sessionOverrideScopeProvider)
: base(principalAccessor, multiTenancy, tenantResolver, sessionOverrideScopeProvider)
{
} public string UserName => GetUserName(ClaimTypes.Name); private string GetUserName(string claimType)
{
var claimsPrincipal = PrincipalAccessor.Principal; var claim = claimsPrincipal?.Claims.FirstOrDefault(c => c.Type == claimType);
if (string.IsNullOrEmpty(claim?.Value))
return null; return claim.Value;
} }
}

然后在登录逻辑中加入以下代码:

  //添加身份信息,以便在AbpSession中使用

            identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName));

就这样,我们在ApplicationService, AbpController 和 AbpApiController任何地方注入IAbpSession,然后AbpSession.Name就能获取到我们登录时候添加的信息.

二,abp的错误机制

如果登录过程中出错怎么办,报错了ABP怎么反应,我们来看一下abp的错误机制.在web应用中,异常通常在MVC Controller actions和Web API Controller actions中处理。当异常发生时,应用程序的用户以某种方式被告知错误的相关信息及原因。果错误在正常的HTTP请求时发生,将会显示一个异常页。如果在AJAX请求中发生错误,服务器发送错误信息到客户端,然后客户端处理错误并显示给用户。在所有的Web请求中处理异常是件乏味且重复的工作。ABP自动化完成异常处理,几乎从不需要显示的处理任何异常。ABP处理所有的异常、记录异常并返回合适、格式化的响应到客户端。在客户端处理这些响应并将错误信息显示给用户。

异常显示,首先我们在ActionResult 随便添加一个异常信息,调试一下看一下结果

   public ActionResult Index()
{ // return View();
  throw new Exception("登录密码错误或用户不存在或用户被禁用。");
}

当然,这个异常可能由另一个方法抛出,而这个方法的调用在这个action里。ABP处理这个异常、记录它并显示'Error.cshtml'视图。你可以自定义这个视图来显示错误。一个示例错误视图(在ABP模板中的默认错误视图):

BP对用户隐藏了异常的细节并显示了一个标准(本地化的)的错误信息,除非你显示的抛出一个UserFriendlyException,UserFriendlyException UserFriendlyException是一个特殊类型的异常,它直接显示给用户。参见下面的示例:

    // GET: Account
public ActionResult Index()
{ // return View();
throw new Abp.UI.UserFriendlyException("登录密码错误或用户不存在或用户被禁用。");
}

浏览器结果:

所以,如果你想显示一个特定的错误信息给用户,那就抛出一个UserFriedlyException(或者一个继承自这个类的异常)。

当然如果是ajax请求里面出错,message API处理JSON对象并显示错误信息给用户。前端应该有相应的错误处理.

  返回总目录:ABP+AdminLTE+Bootstrap Table权限管理系统一期

ABP+AdminLTE+Bootstrap Table权限管理系统第八节--ABP错误机制及AbpSession相关的更多相关文章

  1. ABP+AdminLTE+Bootstrap Table权限管理系统第三节--abp分层体系,实体相关及ABP模块系统

    返回总目录:ABP+AdminLTE+Bootstrap Table权限管理系统一期 ABP模块系统 说了这么久,还没有详细说到abp框架,abp其实基于DDD(领域驱动设计)原则的细看分层如下: 再 ...

  2. ABP+AdminLTE+Bootstrap Table权限管理系统第三节--abp分层体系及实体相关

    说了这么久,还没有详细说到abp框架,abp其实基于DDD(领域驱动设计)原则的细看分层如下: 再看我们项目解决方案如下: JCmsErp.Application,应用层:进行展现层与领域层之间的协调 ...

  3. ABP+AdminLTE+Bootstrap Table权限管理系统一期

       学而时习之,不亦说乎,温顾温知新,可以为师矣. 这也是算是一种学习的方法和态度吧,经常去学习和总结,在博客园看了很多大神的文章,写下一点对于ABP(ABP是“ASP.NET Boilerplat ...

  4. ABP+AdminLTE+Bootstrap Table权限管理系统第五节--WBEAPI及SwaggerUI

    一,Web API ABP的动态WebApi实现了直接对服务层的调用(其实病没有跨过ApiController,只是将ApiController公共化,对于这一点的处理类似于MVC,对服务端的 调用没 ...

  5. ABP+AdminLTE+Bootstrap Table权限管理系统第六节--abp控制器扩展及json封装

    一,控制器AbpController 说完了Swagger ui 我们再来说一下abp对控制器的处理和json的封装. 首先我们定义一个控制器,在新增控制器的时候,控制器会自动继承自AbpContro ...

  6. ABP+AdminLTE+Bootstrap Table权限管理系统第十一节--bootstrap table之用户管理列表

    这张开始bootstrap table,引入项目有两种方法,一种是直接去官网下载 地址:http://bootstrap-table.wenzhixin.net.cn/ 另一种是Nuget引入. 然后 ...

  7. ABP+AdminLTE+Bootstrap Table权限管理系统第十节--AdminLTE模板菜单处理

    上节我们把布局页,也有的临时的菜单,但是菜单不是应该动态加载的么?,所以我们这节来写菜单.首先我们看一下AdminLTE源码里面的菜单以及结构. <aside class="main- ...

  8. ABP+AdminLTE+Bootstrap Table权限管理系统第九节--AdminLTE模板页搭建

    AdminLTE 官网地址:https://adminlte.io/themes/AdminLTE/index2.html 首先去官网下载包下来,然后引入项目. 然后我们在web层添加区域Admin以 ...

  9. ABP+AdminLTE+Bootstrap Table权限管理系统第七节--登录逻辑及abp封装的Javascript函数库

    经过前几节,我们已经解决数据库,模型,DTO,控制器和注入等问题.那么再来看一下登录逻辑.这里算是前面几节的一个初次试水. 首先我们数据库已经有的相应的数据. 模型和DTO已经建好,所以我们直接在服务 ...

随机推荐

  1. ajax数据请求4(xml格式)

    ajax数据请求4(xml格式): <!doctype html> <html> <head> <meta charset="utf-8" ...

  2. 如何将txt的多行记录直接导入到mysql数据库

    1.使用工具是navicat for mysql 2.要导入的txt格式要求,第一行为栏位,及个属性名 第二行开始为数据行 如下所示,例如要插入多行账号密码

  3. 分享一款简单好用的HTML拼接工具

    今天分享一款很好用的字符串拼接工具,在前端开发中,经常需要我们去手动拼接HTML代码,如果你经常这么做,那么肯定会因为单双引号的问题弄得焦头烂额.有了这个拼接工具,妈妈再也不用担心我拼不好html代码 ...

  4. Django开发的基于markdown的博客开源

    PiperMarkdown Blog for Django1.11,Python 3.6,based on Markdown,网址,希望大家能给个star,谢谢! 什么是PiperMarkdown 这 ...

  5. ES语法注意事项

    在函数内部定义全局变量:举个栗子 function fn(){ var str = "hezhi"; } -alert(str) //=>fn不执行的 =>str is ...

  6. [图形学] Chp10 OpenGL三维观察程序示例

    10.10节书中给出了一个程序示例,有一个填充正方形,从侧面的角度观察并画到屏幕上. 图0 这里进一步画出一个立方体,将相机放入立方体中心,旋转相机,达到在立方体中旋转看到不同画面的效果. 步骤: 1 ...

  7. [图形学] 习题8.6 线段旋转后使用Cohen-Sutherland算法裁剪

    习题8.6 生成一条比观察窗口对角线还长的线段动画,线段重点位于观察窗口中心,每一帧的线段在上一帧基础上顺时针旋转一点,旋转后用Cohen-Sutherland线段裁剪算法进行裁剪. 步骤: 1 视口 ...

  8. Microsoft office2010页码设置----论文、课程设计报告格式

    思想:将目录页(含目录页)与目录页以下的页面用分隔符分隔开,单独设置目录页以下的页面页码,删除目录页(含目录)以前的页码. 1.在目录页页面内容最下面一行插入分隔符,实现与下面页面分隔开的目的. 页面 ...

  9. Java 反射在实际开发中的应用

    运行时类型识别(RTTI, Run-Time Type Information)是Java中非常有用的机制,在java中,有两种RTTI的方式,一种是传统的,即假设在编译时已经知道了所有的类型:还有一 ...

  10. php 极简框架ES发布(代码总和不到 400 行)

    ES 框架简介 ES 是一款 极简,灵活, 高性能,扩建性强 的php 框架. 未开源之前在商业公司 经历数年,数个高并发网站 实践使用! 框架结构 整个框架核心四个文件,所有文件加起来放在一起总行数 ...