轉大神文檔:https://www.cnblogs.com/linJie1930906722/p/5708966.html
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)
建議優先選擇第二種方法,因為可以防止以后他人維護時數據成員被意外使用。


