我们知道“依赖注入”已经成为了.NET Core的基本编程模式,表示当前请求上下文的HttpContext可以通过注入的IHttpContextAccessor服务来提取。有时候我们会使用一些由于某些原因无法使用依赖注入的组件,我们如何提取当前HttpContext呢?
要回答这个问题,就得先来了解表示当前HTTP请求上下文的HttpContext对象被存储在什么地方?既然我们可以利用注入的IHttpContextAccessor服务来得到当前HttpContext,针对HttpContext的获取逻辑自然就体现在该接口的实现类型HttpContextAccessor上。于是反编译(也可以直接从github上获取源代码)该类型,得到它的源代码。
代码语言:javascript复制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对象。但是我们又不愿意承受反射带来的性能代价,那个表达式树自然成为了我们的首选解决方案。
代码语言:javascript复制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的提取可以通过如下的程序来验证。
代码语言:javascript复制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();
}
}