采用“传统”方式获取当前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();
}
}
采用“传统”方式获取当前HttpContext的更多相关文章
- Spring-Security (学习记录四)--配置权限过滤器,采用数据库方式获取权限
目录 1. 需要在spring-security.xml中配置验证过滤器,来取代spring-security.xml的默认过滤器 2. 配置securityMetadataSource,可以通过ur ...
- J2EE Web开发入门—通过action是以传统方式返回JSON数据
关键字:maven.m2eclipse.JSON.Struts2.Log4j2.tomcat.jdk7.Config Browser Plugin Created by Bob 20131031 l ...
- centos Linux下磁盘管理 parted,df ,du,fdisk,partprobe,mkfs.ext4,mount,/etc/fstab,fsck,e2fsck,mk2efs,tmpfs ,nr_inodes, LVM,传统方式扩容文件系统 第七节课
centos Linux下磁盘管理 parted,df ,du,fdisk,partprobe,mkfs.ext4,mount,/etc/fstab,fsck,e2fsck,mk2efs,tmpf ...
- 对比传统方式访问数据库和SpringData访问数据库
我们在写代码的时候应该一边写一边测试,这样的话可以尽快的找到错误,在代码写多了之后去找错误的话不容易给错误定位 传统方式访问数据库 1:创建一个Maven web项目 2:修改pom.xml为以下内容 ...
- Mybatis基础:Mybatis映射配置文件,Mybatis核心配置文件,Mybatis传统方式开发
一.Mybatis快速入门 1.1 框架介绍 框架是一款半成品软件,我们可以基于这个半成品软件继续开发,来完成我们个性化的需求! 框架:大工具,我们利用工具,可以快速开发项目 (mybatis也是一个 ...
- java通过jni方式获取硬盘序列号(windows,linux)
linux系统java通过jni方式获取硬盘序列号 http://blog.csdn.net/starter110/article/details/8186788 使用jni在windows下读取硬盘 ...
- 基于uFUN开发板的心率计(一)DMA方式获取传感器数据
前言 从3月8号收到板子,到今天算起来,uFUN到手也有两周的时间了,最近利用下班后的时间,做了个心率计,从单片机程序到上位机开发,到现在为止完成的差不多了,实现很简单,uFUN开发板外加一个Puls ...
- 采用注解方式实现security
采用注解方式使用security,首先我们需要用注解方式实现Spring MVC,新建一个Maven项目 本项目目录结构如下: 我们会发现在WEB-INF中没有web.xml文件,下面会介绍,采用j ...
- 采用DoGet方式提交中文,乱码产生原因分析及解决办法
前段时间某功能在测试机器上出现乱码,情况如下: 现象: 调试搜索功能时,通过doGet方法提交到后台的中文参数在本地和开发测试机器上为乱码(Action层),在测试人员测试机器 ...
随机推荐
- 实验二 HTML中图片和超链接的应用
实验二 HTML中图片和超链接的应用 [实验目的] 1.通过本例要求掌握常见的图像格式及图像的插入方法. 2.能够修改图像属性,利用外部图像处理软件编辑图像. 3.掌握设置各类超级连接的方法. 4.灵 ...
- MeteoInfoLab脚本示例:读取文本文件
此例中的降水文本文件下载自"中国气象科学数据共享服务网"(http://cdc.nmic.cn/sksj.do?method=ssrjscp),其实是ESRI的文本格点数据格式.对 ...
- java 图片相似度算法
利用直方图原理实现图像内容相似度比较 ,作为笔记记录在随笔中. public class PhotoDigest { public static void main(String[] ...
- 为什么大部分的程序员学编程,都会选择从C语言开始?
软件行业经过几十年的发展,编程语言的种类已经越来越多了,而且很多新的编程语言已经在这个领域从开始的默默无闻到如今风风火火,整个编程语言朝着集成化方向发展,这样会导致很多的初学者选择上不像以前那么单一了 ...
- logstash-配置文件详解
kafka 将 kafka topic 中的数据读取为事件 kafka{ bootstrap_servers=> "kafka01:9092,kafka02:9092,kaf ...
- k8s 命令创建pod
[root@master kubernetes]# kubectl create deploy ngx-dep --image=nginx:1.14-alpine deployment.apps/ng ...
- canal 配置 多个监听 推送到不同mq
canal整合rabbitmq 见上篇 如果想监听不同库 不同表到不同队列如何操作 1. vim canal.properties canal.destinations = route1,route ...
- linux(centos8):安装java jdk 14 (java 14.0.2)
一,下载jdk14 官方网站: https://www.oracle.com/java/ 下载页面: https://www.oracle.com/cn/java/technologies/javas ...
- linux(centos8):用sort对文本内容排序
一,sort的用途 1,作用: sort命令用于将文本文件内容加以排序. 它能够以行为单位来排序 2,sort常与uniq搭配使用,原因: 用uniq命令去重时, 需要相同的每行位置相邻才能生效, 所 ...
- 皕杰报表:连接数据库失败,请检查数据源配置(oracle.jdbc.driver.OracleDriver)
皕杰报表:连接数据库失败,请检查数据源配置(oracle.jdbc.driver.OracleDriver)问题解决: 缺少了classes12.jar 在窗口--首选项--报表运行时配置--添加-- ...