解ASP.NET的开发人员都知道它有个很是强大的对象 HttpContext,并且为了方便,ASP.NET还为它提供了一个静态属性HttpContext.Current来访问它。安全
因为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"]; } } }
这样的代码,常常能在类库项目中看到,因而可知其泛滥程度。ide
难道这些代码真的没有问题吗?
有人估计会说:我写的代码是给ASP.NET程序使用的,又不是给控制台程序使用,因此没有问题。工具
真的是这样吗?网站
的确,在一个ASP.NET程序中,几乎任什么时候候,咱们均可以访问HttpContext.Current获得一个HttpContext对象, 然而,您有没有想过它是如何实现的呢?ui
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); }
这就是我看到的结果,不信的话您也能够试试。url
从这段代码来看,HttpContext实际上是保存在CallContext.HostContext这个属性中, 若是还对HostContext感到好奇的话,能够本身用Reflector.exe去看,再也不贴代码了,由于有些类型和方法并非公开的。spa
来看看MSDN是如何解释CallContext.HostContext的吧:【获取或设置与当前线程相关联的主机上下文。】线程
这个解释很是含糊,不过有二个关键词咱们能够记下来:【当前线程】,【关联】。设计
我是这样理解的:和当前线程相关联的某个东西吗?
咱们在一个ASP.NET程序中,为何能够处处访问HttpContext.Current呢?
由于ASP.NET会为每一个请求分配一个线程,这个线程会执行咱们的代码来生成响应结果, 即便咱们的代码散落在不一样的地方(类库),线程仍然会执行它们, 因此,咱们能够在任何地方访问HttpContext.Current获取到与【当前请求】相关的HttpContext对象, 毕竟这些代码是由同一个线程来执行的嘛,因此获得的HttpContext引用也就是咱们期待的那个与请求相关的对象。
所以,将HttpContext.Current设计成与【当前线程】相关联是合适的。
【当前线程】是个什么意思?
答:
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才是更安全的选择。
前面我还提到在APM模式下的异步完成回调时,访问HttpContext.Current也会返回null,那么此时该怎么办呢?
答案有二种:
1. 在类型中添加一个字段来保存HttpContext的引用(异步开始前)。
2. 将HttpContext赋值给BeginXXX方法的最后一个参数(object state)
建议优先选择第二种方法,由于能够防止之后他人维护时数据成员被意外使用。
有时咱们会写些通用类库给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 ) { // 在这里访问与请求有关的东西。 }