• 采用”传统”方式获取当前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();
        }
    }
  • 相关阅读:
    小刘的PHP面试碰到的坑
    小刘的PHP面试碰到的坑
    小刘的项目经验
    04 mysql 深入浅出索引(上)
    CGI,FastCGI和PHP-FPM之间的关系和区别。
    mysql客户端模拟脏读、幻读和可重复读
    mysql 3 | 事务隔离:为什么你改了我还看不见?
    sa账号无法登陆sqlserver2008
    Java单体应用
    Java单体应用
  • 原文地址:https://www.cnblogs.com/artech/p/how-to-get-httpcontext.html
Copyright © 2020-2023  润新知