返回總目錄:ABP+AdminLTE+Bootstrap Table權限管理系統一期html
上一節咱們講到登陸邏輯,我作的登陸邏輯很簡單的,咱們來看一下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涉及到租戶和身份驗證的問題,因此登陸有點繁瑣.分析發現主要包括如下幾個步驟:web
((ClaimsPrincipal)Thread.CurrentPrincipal).Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
須要獲取會話信息則必須實現IAbpSession接口。雖然你能夠用本身的方式去實現它(IAbpSession),可是它在module-zero項目中已經有了完整的實現。IAbpSession包含還有其餘信息.ajax
// // 摘要: // 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。session
3.MultiTenancySide: 多是Host或Tenant。app
UserId和TenantId是能夠爲null的。固然也提供了不爲空時獲取數據的 GetUserId()和GetTenantId() 方法 。當你肯定有當前用戶時,你可使用GetUserId()方法。若是當前用戶爲空,使用該方法則會拋出一個異常。GetTenantId()的使用方式和GetUserId()相似。框架
IAbpSession一般是以屬性注入的方式存在於須要它的類中,不須要獲取會話信息的類中則不須要它。若是咱們使用屬性注入方式,咱們能夠用
NullAbpSession.Instance做爲默認值來初始化它(IAbpSession)async
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是很正常的)。
ApplicationService, AbpController 和 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對象並顯示錯誤信息給用戶。前端應該有相應的錯誤處理.