ABP入門系列(10)——擴展AbpSession

来源:http://www.cnblogs.com/sheng-jie/archive/2017/02/06/6370338.html
-Advertisement-
Play Games

"ABP入門系列目錄——學習Abp框架之實操演練" "源碼路徑:Github LearningMpaAbp" 一、AbpSession是Session嗎? 1、首先來看看它們分別對應的類型是什麼? 查看源碼發現 是定義在Controller中的類型為 的屬性。 再來看看 是何須類也,咱們定位到 中看 ...


ABP入門系列目錄——學習Abp框架之實操演練
源碼路徑:Github-LearningMpaAbp


一、AbpSession是Session嗎?

1、首先來看看它們分別對應的類型是什麼?

查看源碼發現Session是定義在Controller中的類型為HttpSessionStateBase的屬性。
public HttpSessionStateBase Session { get; set; }

再來看看AbpSession是何須類也,咱們定位到AbpController中看一看。
public IAbpSession AbpSession { get; set; }

好吧,原來AbpSession是IAbpSession類型啊。但這就可以斷定AbpSession不是Session嗎?
未必吧,如果IAbpsession的具體實現中還是依賴Session也不一定哦,如果是這樣,那AbpSession可以算作Session的擴展,也可以說是Session。
咱還是找找IAbpsession的具體實現一探究竟吧。
Abp中對IAbpsession有兩個實現方式,一種是NullAbpSessionNullAbpSession是空對象設計模式,用於屬性註入時,在構造函數中對其初始化。
另一種是ClaimsAbpSession,咱們來一探究竟。

2、一探究竟ClaimsAbpSession

以下代碼是ClaimsAbpSession的節選:

/// <summary>
/// Implements <see cref="IAbpSession"/> to get session properties from claims of <see cref="Thread.CurrentPrincipal"/>.
/// </summary>
public class ClaimsAbpSession : IAbpSession, ISingletonDependency
{
    public virtual long? UserId
    {
        get
        {
            var userIdClaim = PrincipalAccessor.Principal?.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
            if (string.IsNullOrEmpty(userIdClaim?.Value))
            {
                return null;
            }

            long userId;
            if (!long.TryParse(userIdClaim.Value, out userId))
            {
                return null;
            }

            return userId;
        }
    }

    public IPrincipalAccessor PrincipalAccessor { get; set; }

    public ClaimsAbpSession(IMultiTenancyConfig multiTenancy)
    {
        MultiTenancy = multiTenancy;
        PrincipalAccessor = DefaultPrincipalAccessor.Instance;
    }
}

其中IPrincipalAccessor又是什麼鬼,從構造函數來看,DefaultPrincipalAccessor應該是個單例模式。

public class DefaultPrincipalAccessor : IPrincipalAccessor, ISingletonDependency
{
   public virtual ClaimsPrincipal Principal => Thread.CurrentPrincipal as ClaimsPrincipal;
   public static DefaultPrincipalAccessor Instance => new DefaultPrincipalAccessor();
}

將上面兩部分代碼一中和,AbpSession中的UserId不就是這樣獲得的:
((ClaimsPrincipal)Thread.CurrentPrincipal).Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);

好了一切一目瞭然了,AbpSession最終依賴的是ClaimsPrincipal,並不是Session

所以AbpSession不是Session!!!
所以AbpSession不是Session!!!
所以AbpSession不是Session!!!

ClaimsPrincipal又是什麼鬼?我就喜歡你這打破砂鍋問到底的勁,且聽我娓娓道來。

二、Identity身份認證

本節主要參考自博客園Savorboard的博文,在此感謝Savorboard的精彩分享,建議大家去細細品讀一番:

ASP.NET Core 之 Identity 入門(一)
ASP.NET Core 之 Identity 入門(二)
ASP.NET Core 之 Identity 入門(三)

1、Cliam(身份信息)

拿身份證舉例,其中包括姓名:奧巴馬、性別:男、民族:xx、出生:xx、住址:xx、公民省份號碼:xxx,這些鍵值對都是身份信息。其中姓名、性別、民族、出生、住址、公民省份號碼這些是身份信息類別(ClaimsType),微軟已經給我們預定義了一系列的身份信息類別,其中包括(Email、Gender、Phone等等)。

2、ClaimsIdentity(身份證)

有了身份信息,一組裝,不就成了身份證。
看下ClaimsIdentity的簡要代碼:

public class ClaimsIdentity: IIdentity
{
    public virtual IEnumerable<Claim> Claims
    {
    get {   //省略其他代碼  }
    }
    
    //名字這麼重要,當然不能讓別人隨便改啊,所以我不許 set,除了我兒子跟我姓,所以是 virtual 的
    public virtual string Name { get; }
    
    //這是我的證件類型,也很重要,同樣不許 set
    public virtual string AuthenticationType { get; }
    
    public virtual void AddClaim(Claim claim);
    
    public virtual void RemoveClaim(Claim claim);
    
    public virtual void FindClaim(Claim claim);
}

可以看到ClaimsIdentity維護了一個Claim枚舉列表。
其中AuthenticationType,從字面意思理解是驗證類型。什麼意思呢?比如我們拿身份證去政府部門辦理業務時,有時需要持本人身份證,但有時候需要身份證複印件即可。

3、ClaimsPrincipal (證件所有者)

我們用身份信息構造了一個身份證,這個身份證肯定是屬於具體的某個人吧。
所以ClaimsPrincipal就是用來維護一堆證件的。
因為現實生活中也是這樣,我們有身份證、銀行卡、社保卡等一系列證件。
那咱們就來看.net中是怎樣實現的:

//核心代碼部分
public class ClaimsPrincipal :IPrincipal
{
    //把擁有的證件都給當事人
    public ClaimsPrincipal(IEnumerable<ClaimsIdentity> identities){}
    
    //當事人的主身份呢
    public virtual IIdentity Identity { get; }
    
    public virtual IEnumerable<ClaimsIdentity> Identities { get; }
    
    public virtual void AddIdentity(ClaimsIdentity identity);
    
    //為什麼沒有RemoveIdentity , 留給大家思考吧?
}

瞭解了這些概念,我們再來看看Identity的簡要登陸流程:

Identity簡要登陸流程

從這張圖來看,我們登陸的時候提供一些身份信息Claim(用戶名/密碼),然後Identity中間件根據這些身份信息構造出一張身份證ClaimsIdentity,然後把身份證交給ClaimsPrincipal證件所有者保管。

三、捋一捋Abp中的登陸流程

定位到AccountController,關註下以下代碼:

[HttpPost]
[DisableAuditing]
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)
{
    var loginResult = await _logInManager.LoginAsync(usernameOrEmailAddress, password, tenancyName);

    switch (loginResult.Result)
    {
        case AbpLoginResultType.Success:
            return loginResult;
        default:
            throw CreateExceptionForFailedLoginAttempt(loginResult.Result, usernameOrEmailAddress, tenancyName);
    }
}

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);
}

分析發現主要包括以下幾個步驟:
1、 GetLoginResultAsync --> loginManager.LoginAsync --> userManager.CreateIdentityAsync:不要以為調用了LoginAsync就以為是登錄,其實這是偽登錄。主要根據用戶名密碼去核對用戶信息,構造User對象返回,然後再根據User對象的身份信息去構造身份證(CliamsIdentity)。
2、SignInAsync --> AuthenticationManager.SignOut
-->AuthenticationManager.SignIn

AuthenticationManager(認證管理員),負責真正的登入登出。SignIn的時候將第一步構造的身份證(CliamsIdentity)交給證件所有者(ClaimsPrincipal)。

是不是明白該怎麼擴展AbpSession了?
關鍵是往身份證(CliamsIdentity)中添加身份信息(Cliam)啊!!!

其實去github上Abp官網搜issue,發現土耳其大牛也是給的這種擴展思路,詳參此鏈

四、開始擴展AbpSession

上一節已經理清了思路,這一節咱們就擼起袖子擴展吧。
ApplicationService, AbpController 和 AbpApiController 這3個基類已經註入了AbpSession屬性。
所以我們需要在領域層,也就是.Core結尾的項目中對AbpSession進行擴展。
現在假設我們需要擴展一個Email屬性。

1、擴展IAbpSession

定位到.Core結尾的項目中,添加Extensions文件夾,然後添加IAbpSessionExtension介面繼承自IAbpSession

namespace LearningMpaAbp.Extensions
{
    public interface IAbpSessionExtension : IAbpSession
    {
        string Email { get; }
    }
}

2、實現IAbpSessionExtension

添加AbpSessionExtension類,基礎自ClaimsAbpSession並實現IAbpSessionExtension介面。

namespace LearningMpaAbp.Extensions
{
    public class AbpSessionExtension : ClaimsAbpSession, IAbpSessionExtension
    {
        public AbpSessionExtension(IMultiTenancyConfig multiTenancy) : base(multiTenancy)
        {
        }

        public string Email => GetClaimValue(ClaimTypes.Email);

        private string GetClaimValue(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;
        }
    }
}

3、替換掉註入的AbpSession屬性

先來替換掉AbpController中註入的AbpSession
定位到.Web\Controllers\xxxxControllerBase.cs,使用屬性註入IAbpSessionExtension。添加以下代碼:

//隱藏父類的AbpSession
public new IAbpSessionExtension AbpSession { get; set; }

再來替換掉ApplicationService中註入的AbpSession
定位到.Application\xxxxAppServiceBase.cs。使用屬性註入IAbpSessionExtension,同樣添加以下代碼:

//隱藏父類的AbpSession
public new IAbpSessionExtension AbpSession { get; set; }

至於AbpApiController要不要替換AbpSession,就視情況而定了,如果你使用的是Abp提供的動態WebApi技術,就不需要替換了,因為畢竟最終調用的是應用服務層的Api。如果WebApi是自己代碼實現的,那就仿照上面自行替換吧,就不羅嗦了。

4、登錄前添加Cliam(身份信息)

定位到AccountController,修改SignInAsync方法,在調用AuthenticationManager.SignIn之前添加下麵代碼:
identity.AddClaim(new Claim(ClaimTypes.Email, user.EmailAddress));

5、無圖無真相

Web層成功獲取擴展的Email

應用服務層成功獲取擴展的Email


本文參考了以下博文,在此再次感謝它們的精彩分享:
ASP.NET Core 之 Identity 入門(一)--Savorboard
ASP.NET Core 之 Identity 入門(二)--Savorboard
ASP.NET Core 之 Identity 入門(三)--Savorboard
Asp.net Boilerplate之AbpSession擴展--kid1412
基於DDD的.NET開發框架 - ABP Session實現--Joye.Net




您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在學習泛型時遇到的困惑經常與func<T,U>混淆,總認為最後一個值是返回類型。現在區分一下,原來問題出在泛型委托上。 C#委托的介紹(delegate、Action、Func、predicate) 委托是一個類,它定義了方法的類型,使得可以將方法當作另一個方法的參數來進行傳遞。事件是一種特殊的委托 ...
  • 簡單的應用場景:分頁獲取日誌JSON信息。 很簡單的實現,簡單的記錄一下 未壓縮時候 使用PostMan請求http://localhost:34390/api/gpm/syslog/page?pageindex=1&pagesize=10 上圖用到了一些技術:AOP、IOC、Request/Res ...
  • 本文供實習司機快速上手log4net最基本功能,共4步,3分鐘搞定。 一、添加log4net.dll引用,可使用nuget安裝或直接引用文件 二、添加配置 在app.config或web.config添加如下配置。註意<configSections> 必須放在最上方。 三、項目AssemblyInf ...
  • 1. 概述 依賴屬性(DependencyProperty)是UWP的核心概念,它是有DependencyObject提供的一種特殊的屬性。由於UWP的幾乎所有UI元素都是集成於DependencyObject的FramewordElement,並且這些UI元素的幾乎所有屬性及它們出現在XAML中的 ...
  • 本篇博客轉載:http://www.cnblogs.com/freeliver54/archive/2009/02/11/1388173.html 本次示例效果如下:Form1為父窗體(包含textBox1、button1)Form2為子窗體(包含textBox2、button2) 父窗體給子窗體傳 ...
  • 和大家分享下當前OSS開源項目的進度情況: 一. OSS.Common 【開源中國】 【github】 經過昨天的努力,oss.common項目初步完成了對.net standard的支持,遷移過程本周以新文章的形式開放給大家,當前解決方案有兩個: OSS.Common.NET40 - 針對原來.n ...
  • .net體系經過十幾年發展,發生了很多變化。特別是在最近兩年,隨著開源和跨平臺的發展,衍生出很多概念,像標準庫,可移植庫,.Net Core等,相信有不少同學對他們之間的關係是有一些困惑的,這裡我從基礎概念,跨框架開發的註意事項等,對.net的平臺和相關概念做一個普及分享。此分享是從個人的知識體系中 ...
  • ServerSuperIO 3.2版本以前,設備數據僅支持Xml序列化的方式,如果以其他方式存儲數據,那麼只能把持久化操作寫在設備驅動中,本質上失去了模塊化的靈活性。3.2 版本以後增加了數據持久化介面,方便支持多種形式存儲設備的參數數據和實時數據,3.2版本里現在僅支持Xml序列化的方式,後期會支... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...