HttpContext.Current并非无处不在

作者:   出处:互联网   2015-07-04 23:26:33   阅读:2

了解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 ) {  // 在这里访问与请求有关的东西。  }  
Copyright© 2006-2015 ComponentCN.com all rights reserved.重庆磐岩科技有限公司(控件中国网) 版权所有 渝ICP备12000264号 法律顾问:元炳律师事务所
客服软件
live chat