采用“传统”方式获取当前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层),在测试人员测试机器 ...
随机推荐
- 多测师讲解接口自动化测试 _requests_高级讲师肖sir
rep=requests.post 错误方法: 1.在代理中---把高级中----代理-----去除勾选,调用失败
- 多测师讲解自动化测试_rf运行无日志(解决方法)_高级讲肖sir
rf运行无日志(解决方法) 出现现象: 方法: 1.先在套件里面导入OperatingSystem库 2.在套件中新建一个关键字套件点击右键选择new user keyword关键 字的名称为:kil ...
- MeteoInfoLab脚本示例:多Y轴图
数据范围相差比较大的数据序列进行对比的时候多Y轴图就很重要了.MeteoInfoLab中提供了一个twinx函数来根据已有的坐标系(Axes)生成一个新的Axes,这个命令会使得已有的Axes不绘制右 ...
- pytest文档53-命令行实时输出错误信息(pytest-instafail)
前言 pytest 运行全部用例的时候,在控制台会先显示用例的运行结果(.或F), 用例全部运行完成后最后把报错信息全部一起抛出到控制台. 这样我们每次都需要等用例运行结束,才知道为什么报错,不方便实 ...
- js实现自定义弹窗
众所周知,浏览器自带的原生弹窗很不美观,而且功能比较单一,绝大部分时候我们都会按照设计图自定义弹窗或者直接使用注入layer的弹窗等等.前段时间在慕课网上看到了一个自定义弹窗的实现,自己顺便就学习尝试 ...
- php超全局数组 为什么swoole的http服务不能用
php的超全局数组$_GET等九个 可以直接使用 无需定义 实际上是浏览器请求到Apache或者nginx的时候 转发到PHP处理模块 fpm转发给php解释器处理 php封装好后丢给php的 sw ...
- Centos定时备份 MySQL数据库
一.编写数据库备份脚本 backupmysql.sh #!/bin/bash # Name:bakmysql.sh # This is a ShellScript For Auto DB Backup ...
- jquery $.ajax 获取josn数据
<script type="text/javascript" src="jquery-1.9.1.js"></script> <s ...
- Prometheus入门教程(三):Grafana 图表配置快速入门
文章首发于[陈树义]公众号,点击跳转到原文:https://mp.weixin.qq.com/s/sA0nYevO8yz6QLRz03qJSw 前面我们使用 Prometheus + Grafana ...
- 字节跳动2020Java面经,你离高薪就只差一片面试题了
前言 经历了惨痛的春招与秋招之后,也积攒了一些面经,希望能对大家有所帮助.由于字数限制需要答案的可以关注GZH[程序员空间] 免费领取完整版PDF 其他 什什么是幂等?什什么情况下需要考虑幂等?你怎么 ...