ASP.NET提供了靜態屬性System.Web.HttpContext.Current,因此獲取HttpContext對象就非常方便了。也正是因為這個原因,所以我們經常能見到直接訪問System.Web.HttpContext.Current的代碼:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 6 namespace Test_HttpContext.Current 7 { 8 public class Test 9 { 10 11 public Test() 12 { 13 string file =System.Web.HttpContext.Current.Request.MapPath("~/App_Data/1.xml"); 14 15 string text = System.IO.File.ReadAllText(file); 16 17 //..........其它的操作 18 } 19 20 // 或者在一些方法中直接使用HttpContext.Current 21 public void Test_1() 22 { 23 string url = System.Web.HttpContext.Current.Request.RawUrl; 24 25 string username = System.Web.HttpContext.Current.Session["username"].ToString(); 26 27 string value = (string)System.Web.HttpContext.Current.Items["key"]; 28 } 29 30 // 甚至還設計成靜態屬性 31 public static string Test_2 32 { 33 get 34 { 35 return (string)System.Web.HttpContext.Current.Items["XXX"]; 36 } 37 } 38 39 /// <summary> 40 /// 獲取文件絕對路徑 41 /// </summary> 42 /// <param name="fileName">文件名稱</param> 43 /// <returns></returns> 44 public string Test_3(string fileName) 45 { 46 return System.Web.HttpContext.Current.Server.MapPath("~/Log" + fileName); 47 } 48 49 } 50 }
上面的這些代碼這樣寫真的沒有問題嗎?
答案是否定的
請看下面的驗證:
我們先來看看HttpContext到底存儲在哪里:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using System.Web.UI; 6 using System.Web.UI.WebControls; 7 8 namespace Test_HttpContext.Current 9 { 10 public partial class WebForm1 : System.Web.UI.Page 11 { 12 protected void Page_Load(object sender, EventArgs e) 13 { 14 15 HttpContext context1 = System.Web.HttpContext.Current; 16 17 HttpContext context2 = System.Runtime.Remoting.Messaging.CallContext.HostContext as HttpContext; //當前(請求)線程上下文 18 19 bool isEqual = object.ReferenceEquals(context1, context2); 20 21 Response.Write("context1與context2是否相同的實例:" + isEqual); 22 } 23 } 24 }
上面的代碼運行的結果是true:
從這段代碼來看,HttpContext其實是保存在System.Runtime.Remoting.Messaging.CallContext.HostContext這個屬性中, System.Runtime.Remoting.Messaging.CallContext.HostContext在MSDN的解釋是 獲取或設置與當前線程相關聯的主機上下文
我們在一個ASP.NET程序中,為什么可以到處訪問HttpContext.Current呢?
因為ASP.NET會為每個請求分配一個線程(也是當前線程),這個線程會執行我們的代碼來生成響應結果, 即使我們的代碼散落在不同的地方(類庫),線程仍然會執行它們, 所以我們可以在任何地方訪問System.Web.HttpContext.Current獲取到與當前請求相關的HttpContext對象, 這些代碼是由同一個線程來執行,所以得到的HttpContext引用也就是我們期待的那個與請求相關的對象。
當前線程是什么意思?
我的理解是:
1. 當前線程是指與當前請求相關的線程。
2. 在ASP.NET程序中,有些線程並非總是與請求相關。
雖然在ASP.NET程序中,幾乎所有的線程都應該是為響應請求而運行的,但是還有一些線程卻不是為了響應請求而(產生)運行的,
例如:
1. 定時器的回調。
2. Cache的移除通知。
3. APM模式下異步完成回調。
4. 主動創建線程或者將任務交給線程池來執行。
5.異步任務Task
至於什么APM網上資料很多,這里我就不說明了
在這些情況下使用System.Web.HttpContext.Current獲取HttpContext對象得到的結果都是null,因為處理他們的線程不是當前線程(為處理請求產生線程)
說的這里我們再回頭看看本文開始寫的(部分)代碼:
1 /// <summary> 2 /// 獲取文件絕對路徑 3 /// </summary> 4 /// <param name="fileName">文件名稱</param> 5 /// <returns></returns> 6 public string Test_3(string fileName) 7 { 8 return System.Web.HttpContext.Current.Server.MapPath("~/Log" + fileName); 9 }
如果這段代碼在那5種情況下運行,都會拋空指針異常,因為System.Web.HttpContext.Current得到是null。
為什么會得到null呢?
因為運行這段代碼線程不是處理當前請求的當前線程
為什么其他地方得到又不是null呢?
因為ASP.NET程序在調用您的代碼前,已經將HttpContext對象設置到前面所說的System.Runtime.Remoting.Messaging.CallContext.HostContext屬性中。
HttpApplication有個內部方法OnThreadEnter(),ASP.NET在調用外部代碼前會調用這個方法來切換HttpContext, 例如:每當執行管線的事件處理器之前,或者同步上下文(AspNetSynchronizationContext)執行回調時。 切換線程的CallContext.HostContext屬性之后,我們的代碼就可以訪問到HttpContext引用。 注意:HttpContext的引用其實是保存在HttpApplication對象中。
這種情況下該如何獲取文件的絕對路徑呢?
我們可以訪問System.Web.HttpRuntime.AppDomainAppPath獲取程序的根路徑,然后再拼接文件的相對路徑即可
上面的代碼得到的HttpContext對象是null,再調用MapPath來獲取站點根目錄,就必死無疑了!
所以在此建議大家在獲取程序(站點)的根目錄時盡量使用System.Web.HttpRuntime.AppDomainAppPath進行獲取站點的根目錄
那么在異步調用調用時訪問HttpContext對象呢?
前面我還提到在APM模式下的異步完成回調時,訪問HttpContext.Current也會返回null,那么此時該怎么辦呢?
1. 在類型中添加一個字段來保存HttpContext的引用(異步開始前)。
2. 將HttpContext賦值給BeginXXX方法的最后一個參數(object state)
建議優先選擇第二種方法,因為可以防止以后他人維護時數據成員被意外使用。