HttpContext.Current 的缺陷
了解ASP.NET的开发人员都知道它有个非常强大的对象 HttpContext,而且为了方便,ASP.NET还为它提供了一个静态属性HttpContext.Current来访问它,今天的博客打算就从HttpContext.Current说起。
无处不在的HttpContext
由于ASP.NET提供了静态属性HttpContext.Current,因此获取HttpContext对象就非常方便了。
也正是因为这个原因,所以我们经常能见到直接访问HttpContext.Current的代码:
public class Class1
{
public Class1()
{
string file = HttpContext.Current.Request.MapPath("~/App_Data/xxxxxx.xml"); string text = System.IO.File.ReadAllText(file); //..........其它的操作
} // 或者在一些方法中直接使用HttpContext.Current
public void XXXXX()
{
string url = HttpContext.Current.Request.RawUrl; string username = HttpContext.Current.Session["username"].ToString(); string value = (string)HttpContext.Current.Items["key"];
} // 甚至还设计成静态属性
public static string XXX
{
get
{
return (string)HttpContext.Current.Items["XXX"];
}
}
}
这样的代码,经常能在类库项目中看到,由此可见其泛滥程度。
难道这些代码真的没有问题吗?
有人估计会说:我写的代码是给ASP.NET程序使用的,又不是给控制台程序使用,所以没有问题。
真的是这样吗?
HttpContext.Current到底保存在哪里?
的确,在一个ASP.NET程序中,几乎任何时候,我们都可以访问HttpContext.Current得到一个HttpContext对象,然而,您有没有想过它是如何实现的呢?
如果您没有想过这个事情,那我今天就来告诉您吧。请看下面的代码:
protected void Page_Load(object sender, EventArgs e)
{
HttpContext context1 = HttpContext.Current; HttpContext context2 = System.Runtime.Remoting.Messaging.CallContext.HostContext as HttpContext; bool isEqual = object.ReferenceEquals(context1, context2); Response.Write(isEqual);
}
猜猜会显示什么?

这就是我看到的结果,不信的话您也可以试试。
从这段代码来看,HttpContext其实是保存在CallContext.HostContext这个属性中,如果您还对HostContext感到好奇的话,您可以自己用Reflector.exe去看,我不想再贴代码了,因为有些类型和方法并不是公开的。
我们还是来看看MSDN是如何解释CallContext.HostContext的吧:
获取或设置与当前线程相关联的主机上下文。
这个解释非常含糊,不过有二个关键词我们可以记下来:【当前线程】,【关联】。
是说:和当前线程相关联的某个东西吗?
我是这样理解的。
我们在一个ASP.NET程序中,为什么可以到处访问HttpContext.Current呢?
因为ASP.NET会为每个请求分配一个线程,这个线程会执行我们的代码来生成响应结果,即使我们的代码散落在不同的地方(类库),线程仍然会执行它们,所以,我们可以在任何地方访问HttpContext.Current获取到与【当前请求】相关的HttpContext对象,毕竟这些代码是由同一个线程来执行的嘛,所以得到的HttpContext引用也就是我们期待的那个与请求相关的对象。
因此,将HttpContext.Current设计成与【当前线程】相关联是合适的。
HttpContext并非无处不在!
【当前线程】是个什么意思? 我为什么要突出这个词呢?
答:
1. 当前线程是指与【当前请求】相关的线程。
2. 在ASP.NET中,有些线程并非总是与请求相关。
感觉有点绕口吗? 不容易理解吗? 还是继续往下看吧。
虽然在ASP.NET程序中,几乎所有的线程都应该是为响应请求而运行的,
但是,还有一些线程却不是为了响应请求而运行,例如:
1. 定时器的回调。
2. Cache的移除通知。
3. APM模式下异步完成回调。
4. 主动创建线程或者将任务交给线程池来执行。
在以上这些情况中,如果线程执行到HttpContext.Current,您认为会返回什么?
还是一个HttpContext的实例引用吗?
如何是,那它与哪个请求关联?
显然,在1,2二种情况中,访问HttpContext.Current将会返回 null 。
因为很有可能任务在运行时根本没有任何请求发生。
了解异步的人应该能很容易理解第3种情况(就当是个结论吧)
第4种情况就更不需要解释了,因为确实不是当前线程。
既然是这样,那我们再看一下本文开头的一段代码:
public Class1()
{
string file = HttpContext.Current.Request.MapPath("~/App_Data/xxxxxx.xml"); string text = System.IO.File.ReadAllText(file); //..........其它的操作
}
想像一下:如果Class1是在定时器回调或者Cache的移除通知时被创建的,您认为它还能正常运行吗?
此刻您心里应该有答案了吧?
可能您会想:为什么我在其它任何地方又可以访问HttpContext.Current得到HttpContext引用呢?
答:那是因为ASP.NET在调用您的代码前,已经将HttpContext设置到前面所说的CallContext.HostContext属性中。
HttpApplication有个内部方法OnThreadEnter(),ASP.NET在调用外部代码前会调用这个方法来切换HttpContext,例如:每当执行管线的事件处理器之前,或者同步上下文(AspNetSynchronizationContext)执行回调时。切换线程的CallContext.HostContext属性之后,我们的代码就可以访问到HttpContext引用。注意:HttpContext的引用其实是保存在HttpApplication对象中。
有时候我们会见到【ASP.NET线程】这个词,今天正好来说说我对这个词的理解:当前线程是与一个HttpContext相关的线程,由于线程与HttpContext相关联,也就意味着它正在处理发送给ASP.NET的请求。注意:这个线程仍然是线程池的线程。
如何获取文件绝对路径?
在定时器回调或者Cache的移除通知中,有时确实需要访问文件,然而对于开发人员来说,他们并不知道网站会被部署在哪个目录下,因此不可能写出绝对路径,他们只知道相对于网站根目录的相对路径,为了定位文件路径,只能调用HttpContext.Current.Request.MapPath或者 HttpContext.Current.Server.MapPath来获取文件的绝对路径。如果HttpContext.Current返回了null,那该如何如何访问文件?
其实方法并非MapPath一种,我们可以访问HttpRuntime.AppDomainAppPath获取网站的路径,然后再拼接文件的相对路径即可:

看到没:图片中HttpContext.Current显示的是 null ,所以您要是再调用MapPath,就必死无疑!
在此我也奉劝大家一句:尽量不要用MapPath,HttpRuntime.AppDomainAppPath才是更安全的选择。
异步调用中如何访问HttpContext?
前面我还提到在APM模式下的异步完成回调时,访问HttpContext.Current也会返回null,那么此时该怎么办呢?
答案有二种:
1. 在类型中添加一个字段来保存HttpContext的引用(异步开始前)。
2. 将HttpContext赋值给BeginXXX方法的最后一个参数(object state)
建议优先选择第二种方法,因为可以防止以后他人维护时数据成员被意外使用。
安全地使用HttpContext.Current
有时我们会写些通用类库给ASP.NET或者WindowsService程序来使用,例如异常记录的工具方法。对于ASP.NET程序来说,我们肯定希望在异常发生时,能记录URL,表单值,Cookie等等数据,便于事后分析。然而对于WindowsService这类程序来说,您肯定没想过要记录Cookie吧?那么如何实现一个通用的功能呢?
方法其实也简单,就是要判断HttpContext.Current是否返回null,例如下面的示例代码:
public static void LogException(Exception ex)
{
StringBuilder sb = new StringBuilder();
sb.Append("异常发生时间:").AppendLine(DateTime.Now.ToString());
sb.AppendLine(ex.ToString()); // 如果是ASP.NET程序,还需要记录URL,FORM, COOKIE之类的数据
HttpContext context = HttpContext.Current;
if( context != null ) {
// 能运行到这里,就肯定是在处理ASP.NET请求,我们可以放心地访问Request的所有数据
sb.AppendLine("Url:" + context.Request.RawUrl); // 还有记录什么数据,您自己来实现吧。
} System.IO.File.AppendAllText("日志文件路径", sb.ToString());
}
就是一个判断,解决了所有问题,所以请忘记下面这类不安全的写法吧:
HttpContext.Current.Request.RawUrl;
HttpContext.Current.Server.MapPath("xxxxxx");
下面的方法才是安全的:
HttpContext context = HttpContext.Current;
if( context != null ) {
// 在这里访问与请求有关的东西。
}
HttpContext.Current 的缺陷的更多相关文章
- 慎用System.Web.HttpContext.Current
每当控制流离开页面派生的Web表单上的代码的时候,HttpContext类的静态属性Current可能是有用的. 使用这个属性,我们可以获取当前请求(Request),响应(Response),会话( ...
- 异步 HttpContext.Current 为空null 另一种解决方法
1.场景 在导入通讯录过程中,把导入的失败.成功的号码数进行统计,然后保存到session中,客户端通过轮询显示状态. 在实现过程中,使用的async调用方法,出现HttpContext.Curren ...
- 解决Asp.net Mvc中使用异步的时候HttpContext.Current为null的方法
在项目中使用异步(async await)的时候发现一个现象,HttpContext.Current为null,导致一系列的问题. 上网查了一些资料后找到了一个对象: System.Threading ...
- System.Web.HttpContext.Current.Session为NULL解决方法
http://www.cnblogs.com/tianguook/archive/2010/09/27/1836988.html 自定义 HTTP 处理程序,从IHttpHandler继承,在写Sys ...
- Why is HttpContext.Current null after await?
今天在对项目代码进行异步化改进的时候,遇到一个奇怪的问题(莫笑,以前没遇过),正如标题一样,HttpContext.Current 在 await 异步执行之后,就会变为 null. 演示代码: pu ...
- 在ASP.NET Core中怎么使用HttpContext.Current
一.前言 我们都知道,ASP.NET Core作为最新的框架,在MVC5和ASP.NET WebForm的基础上做了大量的重构.如果我们想使用以前版本中的HttpContext.Current的话,目 ...
- HttpContext.Current.Session.SessionID相关问题及备忘
今天Tony提到说我们系统中会利用如下代码来判断用户是否过期. if (string.IsNullOrEmpty(UserContext.ConnectionSessionId)) { LogUIFa ...
- 【C#】关于HttpContext.Current.Request.QueryString 你要知道点
HttpContext.Current.Request.QueryString[ ]括号中是获取另一个页面传过的的参数值 HttpContext.Current.Request.Form[“ID”]· ...
- HttpContext.Current.User is null after installing .NET Framework 4.5
故障原因:从framework4.0到framework4.5的升级过程中,原有的form认证方式发生了变化,所以不再支持User.Identity.Name原有存储模式(基于cookie),要恢复这 ...
随机推荐
- SecureCRT SSH VMware Ubuntu
Ubuntu 10.4 装完后网络OK. NAT模式下, 可以上网. 宿主机和客户机可以彼此ping通. 主要是检查SSH服务, 和防火墙是否关闭(UBUNTU 默认没有安装SSH, 默认启动了防火墙 ...
- VC更换图标文件
步骤 1. 删除图标 2. 在VC中移除图标 3. 用新图标替换原来图标 4. 添加图标到VC 5. 重新编译 参考文档 http://www.cnblogs.com/BloodAndBone/arc ...
- php日期时间函数 整理
设定系统默认时区 date_default_timezone_get() $tz='America/Los_Angeles'; 返回系统默认时区 date_default_timezone_set($ ...
- Prism&MEF构建开发框架 (一)
Shell框架XECA shell.xaml主要起到是一个容器或壳的作用 <Window x:Class="XECA.Shell" xmlns="http ...
- java中的trim()
trim():去掉字符串首尾的空格.但该方法并不仅仅是去除空格,它能够去除从编码'\u0000′ 至 '\u0020′ 的所有字符. 回车换行也在这20个字符 例1: public static vo ...
- shell 使用for循环 启动后台任务
为了统计多天的数据并按照天为文件名输出,写了脚本,脚本可以统计单天的数据.为了实现多天的同时进行采用 启动多个进程后台执行形式: 但是直接 执行的参数后面加上& 并不能解决,采用 echo & ...
- 【转】Android中Application类用法
转自:http://www.cnblogs.com/renqingping/archive/2012/10/24/Application.html Application类 Application和A ...
- CXF入门例子
1. WebService实现类:@WebService注解表示这个类发布为一个WebService服务. package com.coshaho.learn.cxf; import javax.jw ...
- JavaScript:JavaScript中常见获取对象元素的方法
介绍: javascript中常见的3种获取元素的方法,分别是通过元素ID.通过标签名字和通过类名字来获取 操作如下: 1.getElementById DOM提供了一个名为getElementByI ...
- jQuery选择器总结(转)
? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 ...