全局獲取HttpContext

全局獲取HttpContext

在我們平常開發中會有這樣的需求,我們的Service業務層需要獲取請求上下文中的用戶信息,一般我們從控制器參數傳遞過來。如果你覺得這樣就可以了,請您關閉文章。

場景

但是我們也會遇到控制器傳遞困難的場景,我自己最近使用單庫實現多租戶的PAAS平臺,發現EF Core上下文獲取我Token或者Headers中獲取租戶Id進行全局過濾就很麻煩(多租戶解決方案後期我補充)。

涉及知識

我們先要知道一個思想如果想要整個.NET程序中共享一個變量,我們可以將想要共享的變量放在某個類的靜態屬性上來實現。
但是我們的請求上下文每個人的信息不一樣,就需要將這個變量的共享範圍縮小到單個線程內。例如在web應用中,服務器爲每個同時訪問的請求分配一個獨立的線程,我們要在這些獨立的線程中維護自己的當前訪問用戶的信息時,就需要線程本地存儲了。

  • IHttpContextAccessor 設置實現規範
  • HttpContextAccessor 基於當前執行上下文提供的實現。
  • AsyncLocal 實現多線程中靜態變量獨立化 (這裏畫一個圈圈)

這個時候我們再看源碼思路就清晰了,我們通過注入HttpContextAccessor,然後內部將請求上下文保存在_httpContextCurrent靜態變量中,這個就可以全局訪問啦(當然訪問範圍是在該主線程內部)。

    // HttpContextAccessor源碼
    public class HttpContextAccessor : IHttpContextAccessor
    {
        // 通過AsyncLocal保存當前上下文信息
        private static readonly AsyncLocal<HttpContextHolder> _httpContextCurrent =new AsyncLocal<HttpContextHolder>();

        public HttpContext? HttpContext
        {
            get
            {
                return  _httpContextCurrent.Value?.Context;
            }
            set
            {
                var holder = _httpContextCurrent.Value;
                if (holder != null)
                {
                    // 清除AsyncLocals中捕獲的當前HttpContext
                    holder.Context = null;
                }
                if (value != null)
                {
                    // 使用一個對象間接在AsyncLocal中保存HttpContext,
                    // 所以當它被清除時,它可以在所有的ExecutionContexts中被清除。
                    _httpContextCurrent.Value = new HttpContextHolder { Context = 
value };
                }
            }
        }

        private class HttpContextHolder
        {
            public HttpContext? Context;
        }

整活

首先我們需要在Startup的ConfigureServices方法中註冊IHttpContextAccessor的實例

public void ConfigureServices(IServiceCollection services)
{
      services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
      ....
}

這個時候你Service層注入該類的時候就可以獲取到請求上下文信息了,但是這個就不符合我們詩一般程序員的氣質。
因爲直接將請求上下文拋出來不友好,我們本來只需要租戶ID但是你給我一坨,挺不好把握的。

整大活

我們可以進行包裝,我使用PrincipalAccessor進行請求上下文拆解

然後在Startup的ConfigureServices方法中,我們一樣把這個類也加入註冊中

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    services.AddSingleton<IPrincipalAccessor, PrincipalAccessor>();
    ....
}

最後自己項目的一些優化

自己不斷的在優化自己的項目結構,或者設計思路,我發現我爲什麼有這麼多注入,我構造函數都要爆了。

然後自己想了想,我其實可以將訪問上下文的類放入BaseService中靜態變量存儲,系統提供了IServiceCollection來註冊服務和提供了IServiceProvider這個讓我們解析各種註冊過的服務.
我們定義一個存儲類

public class ServiceProviderInstance
 {
      public static IServiceProvider Instance { get; set; }
 } 
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
 {
        ...
       ServiceProviderInstance.Instance = app.ApplicationServices;
 }

寶貝相信我剩下的我們交給時間,我們只需要這樣(BaseService定義屬性、獲取注入就可以了),然後就那樣(就直接可以使用啦)

    public class BaseService<T, Repository> : IBaseService<T>
          where T : BaseEntityCore, new()
          //規定這個Repository類型一定是繼承倉儲的接口,下面就可以使用接口的方法
          where Repository : IBaseRepository<T>
    {
        /// <summary>
        /// 身份信息
        /// </summary>
        protected IClaimsAccessor Claims { get; set; }

        /// <summary>
        /// 獲取倉儲實體
        /// </summary>
        private readonly Repository CurrentRepository;

        public BaseService(Repository currentRepository)
        {
            CurrentRepository = currentRepository;
            Claims = ServiceProviderInstance.Instance.GetRequiredService<IClaimsAccessor>();
        }
        .....
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章