采用”傳統”方式獲取當前HttpContext


我們知道“依賴注入”已經成為了.NET Core的基本編程模式,表示當前請求上下文的HttpContext可以通過注入的IHttpContextAccessor服務來提取。有時候我們會使用一些由於某些原因無法使用依賴注入的組件,我們如何提取當前HttpContext呢?

要回答這個問題,就得先來了解表示當前HTTP請求上下文的HttpContext對象被存儲在什么地方?既然我們可以利用注入的IHttpContextAccessor服務來得到當前HttpContext,針對HttpContext的獲取邏輯自然就體現在該接口的實現類型HttpContextAccessor上。於是反編譯(也可以直接從github上獲取源代碼)該類型,得到它的源代碼。

public class HttpContextAccessor : IHttpContextAccessor
{
    // Fields
    private static AsyncLocal<HttpContextHolder> _httpContextCurrent = new AsyncLocal<HttpContextHolder>();

    // Properties
    public HttpContext HttpContext
    {
        get
        {
            HttpContextHolder local1 = _httpContextCurrent.Value;
            if (local1 != null)
            {
                return local1.Context;
            }
            HttpContextHolder local2 = local1;
            return null;
        }
        set
        {
            HttpContextHolder holder = _httpContextCurrent.Value;
            if (holder != null)
            {
                holder.Context = null;
            }
            if (value != null)
            {
                HttpContextHolder holder1 = new HttpContextHolder();
                holder1.Context = value;
                _httpContextCurrent.set_Value(holder1);
            }
        }
    }

    // Nested Types
    private class HttpContextHolder
    {
        // Fields
        public HttpContext Context;
    }
}

上代碼片段可以看出,當前HttpContext被存儲在靜態字段表示的一個AsyncLocal<HttpContextHolder> 對象上(HttpContext被HttpContextHolder對象進一步封裝),這也是為何ASP.NET Core處理請求異步調用鏈(通過await關鍵字)總是可以獲取當前HttpContext的原因所在。但是這里涉及到的HttpContextHolder是一個內嵌私有類型,所以我們只有通過反射的方式來獲取它封裝的HttpContext對象。但是我們又不願意承受反射帶來的性能代價,那個表達式樹自然成為了我們的首選解決方案。

public static class HttpContextUtility
{
    private static Func<object> _asyncLocalAccessor;
    private static Func<object, object> _holderAccessor;
    private static Func<object, HttpContext> _httpContextAccessor;
    public static HttpContext GetCurrentHttpContext()
    {
        var asyncLocal = (_asyncLocalAccessor ??= CreateAsyncLocalAccessor())();
        if (asyncLocal == null)
        {
            return null;
        }

        var holder = (_holderAccessor ??= CreateHolderAccessor(asyncLocal))(asyncLocal);
        if (holder == null)
        {
            return null;
        }

        return (_httpContextAccessor ??= CreateHttpContextAccessor(holder))(holder);

        static Func<object> CreateAsyncLocalAccessor()
        {
            var fieldInfo = typeof(HttpContextAccessor).GetField("_httpContextCurrent", BindingFlags.Static | BindingFlags.NonPublic);
            var field = Expression.Field(null, fieldInfo);
            return Expression.Lambda<Func<object>>(field).Compile();
        }

        static Func<object, object> CreateHolderAccessor(object asyncLocal)
        {
            var holderType = asyncLocal.GetType().GetGenericArguments()[0];
            var method = typeof(AsyncLocal<>).MakeGenericType(holderType).GetProperty("Value").GetGetMethod();
            var target = Expression.Parameter(typeof(object));
            var convert = Expression.Convert(target, asyncLocal.GetType());
            var getValue = Expression.Call(convert, method);
            return Expression.Lambda<Func<object, object>>(getValue, target).Compile();
        }

        static Func<object, HttpContext> CreateHttpContextAccessor(object holder)
        {
            var target = Expression.Parameter(typeof(object));
            var convert = Expression.Convert(target, holder.GetType());
            var field = Expression.Field(convert, "Context");
            var convertAsResult = Expression.Convert(field, typeof(HttpContext));
            return Expression.Lambda<Func<object, HttpContext>>(convertAsResult, target).Compile();
        }
    }

}

上面的代碼體現了采用表達式樹實現的針對當前HttpContext的獲取邏輯。具體來說,靜態方法GetCurrentHttpContext利用表達式創建的Func<object>對象得到HttpContextAccessor靜態字段_httpContextAccessor存儲的AsyncLocal<HttpContextHolder>,然后再利用表達式創建的Func<object, object>得到該對象Value屬性表示的HttpContextHolder對象。我們最終獲得的HttpContext是通過由表達式創建的另一個Func<object,object>從HttpContextHolder對象中提取出來的。GetCurrentHttpContext針對當前HttpContext的提取可以通過如下的程序來驗證。

public class Program
{
    public static void Main(string[] args)
    {
        Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(web => web
                .ConfigureServices(svcs => svcs.AddHttpContextAccessor())
                .Configure(app => app.Run(httpContext =>
                {
                    var httpContextAccessor = httpContext.RequestServices.GetRequiredService<IHttpContextAccessor>();
                    Debug.Assert(ReferenceEquals(httpContext, HttpContextUtility.GetCurrentHttpContext()));
                    Debug.Assert(ReferenceEquals(httpContextAccessor.HttpContext, HttpContextUtility.GetCurrentHttpContext()));
                    return httpContext.Response.WriteAsync("Hello world.");
                })))
            .Build()
            .Run();
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM