新聞中心
了解ASP.NET的開發(fā)人員都知道它有個非常強大的對象 HttpContext,而且為了方便,ASP.NET還為它提供了一個靜態(tài)屬性HttpContext.Current來訪問它,今天的博客打算就從HttpContext.Current說起。

為攸縣等地區(qū)用戶提供了全套網(wǎng)頁設(shè)計制作服務(wù),及攸縣網(wǎng)站建設(shè)行業(yè)解決方案。主營業(yè)務(wù)為成都做網(wǎng)站、網(wǎng)站制作、攸縣網(wǎng)站設(shè)計,以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠的服務(wù)。我們深信只要達到每一位用戶的要求,就會得到認(rèn)可,從而選擇與我們長期合作。這樣,我們也可以走得更遠!
無處不在的HttpContext
由于ASP.NET提供了靜態(tài)屬性HttpContext.Current,因此獲取HttpContext對象就非常方便了。也正是因為這個原因,所以我們經(jīng)常能見到直接訪問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"];
- }
- // 甚至還設(shè)計成靜態(tài)屬性
- public static string XXX
- {
- get
- {
- return (string)HttpContext.Current.Items["XXX"];
- }
- }
- }
這樣的代碼,經(jīng)常能在類庫項目中看到,由此可見其泛濫程度。
難道這些代碼真的沒有問題嗎?
有人估計會說:我寫的代碼是給ASP.NET程序使用的,又不是給控制臺程序使用,所以沒有問題。
真的是這樣嗎?
HttpContext.Current到底保存在哪里?
的確,在一個ASP.NET程序中,幾乎任何時候,我們都可以訪問HttpContext.Current得到一個HttpContext對象,然而,您有沒有想過它是如何實現(xiàn)的呢?
如果您沒有想過這個事情,那我今天就來告訴您吧。請看下面的代碼
- 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);
- }
猜猜會顯示什么?
這就是我看到的結(jié)果,不信的話您也可以試試。
從這段代碼來看,HttpContext其實是保存在CallContext.HostContext這個屬性中,如果您還對HostContext感到好奇的話,您可以自己用Reflector.exe去看,我不想再貼代碼了,因為有些類型和方法并不是公開的。
我們還是來看看MSDN是如何解釋CallContext.HostContext的吧:
獲取或設(shè)置與當(dāng)前線程相關(guān)聯(lián)的主機上下文。
這個解釋非常含糊,不過有二個關(guān)鍵詞我們可以記下來:【當(dāng)前線程】,【關(guān)聯(lián)】。
是說:和當(dāng)前線程相關(guān)聯(lián)的某個東西嗎?
我是這樣理解的。
我們在一個ASP.NET程序中,為什么可以到處訪問HttpContext.Current呢?
因為ASP.NET會為每個請求分配一個線程,這個線程會執(zhí)行我們的代碼來生成響應(yīng)結(jié)果,即使我們的代碼散落在不同的地方(類庫),線程仍然會執(zhí)行它們,所以,我們可以在任何地方訪問HttpContext.Current獲取到與【當(dāng)前請求】相關(guān)的HttpContext對象,畢竟這些代碼是由同一個線程來執(zhí)行的嘛,所以得到的HttpContext引用也就是我們期待的那個與請求相關(guān)的對象。
因此,將HttpContext.Current設(shè)計成與【當(dāng)前線程】相關(guān)聯(lián)是合適的。
HttpContext并非無處不在!
【當(dāng)前線程】是個什么意思? 我為什么要突出這個詞呢?
答:
1. 當(dāng)前線程是指與【當(dāng)前請求】相關(guān)的線程。
2. 在ASP.NET中,有些線程并非總是與請求相關(guān)。
感覺有點繞口嗎? 不容易理解嗎? 還是繼續(xù)往下看吧。
雖然在ASP.NET程序中,幾乎所有的線程都應(yīng)該是為響應(yīng)請求而運行的,
但是,還有一些線程卻不是為了響應(yīng)請求而運行,例如:
1. 定時器的回調(diào)。
2. Cache的移除通知。
3. APM模式下異步完成回調(diào)。
4. 主動創(chuàng)建線程或者將任務(wù)交給線程池來執(zhí)行。
在以上這些情況中,如果線程執(zhí)行到HttpContext.Current,您認(rèn)為會返回什么?
還是一個HttpContext的實例引用嗎?
如何是,那它與哪個請求關(guān)聯(lián)?
顯然,在1,2二種情況中,訪問HttpContext.Current將會返回 null 。
因為很有可能任務(wù)在運行時根本沒有任何請求發(fā)生。了解異步的人應(yīng)該能很容易理解第3種情況(就當(dāng)是個結(jié)論吧)
第4種情況就更不需要解釋了,因為確實不是當(dāng)前線程。
既然是這樣,那我們再看一下本文開頭的一段代碼:
- public Class1()
- {
- string file = HttpContext.Current.Request.MapPath("~/App_Data/xxxxxx.xml");
- string text = System.IO.File.ReadAllText(file);
- //..........其它的操作
- }
想像一下:如果Class1是在定時器回調(diào)或者Cache的移除通知時被創(chuàng)建的,您認(rèn)為它還能正常運行嗎?
此刻您心里應(yīng)該有答案了吧?
可能您會想:為什么我在其它任何地方又可以訪問HttpContext.Current得到HttpContext引用呢?
答:那是因為ASP.NET在調(diào)用您的代碼前,已經(jīng)將HttpContext設(shè)置到前面所說的CallContext.HostContext屬性中。
HttpApplication有個內(nèi)部方法OnThreadEnter(),ASP.NET在調(diào)用外部代碼前會調(diào)用這個方法來切換HttpContext,例如:每當(dāng)執(zhí)行管線的事件處理器之前,或者同步上下文(AspNetSynchronizationContext)執(zhí)行回調(diào)時。切換線程的CallContext.HostContext屬性之后,我們的代碼就可以訪問到HttpContext引用。注意:HttpContext的引用其實是保存在HttpApplication對象中。
有時候我們會見到【ASP.NET線程】這個詞,今天正好來說說我對這個詞的理解:當(dāng)前線程是與一個HttpContext相關(guān)的線程,由于線程與HttpContext相關(guān)聯(lián),也就意味著它正在處理發(fā)送給ASP.NET的請求。注意:這個線程仍然是線程池的線程。
如何獲取文件絕對路徑?
在定時器回調(diào)或者Cache的移除通知中,有時確實需要訪問文件,然而對于開發(fā)人員來說,他們并不知道網(wǎng)站會被部署在哪個目錄下,因此不可能寫出絕對路徑,他們只知道相對于網(wǎng)站根目錄的相對路徑,為了定位文件路徑,只能調(diào)用HttpContext.Current.Request.MapPath或者 HttpContext.Current.Server.MapPath來獲取文件的絕對路徑。如果HttpContext.Current返回了null,那該如何如何訪問文件?
其實方法并非MapPath一種,我們可以訪問HttpRuntime.AppDomainAppPath獲取網(wǎng)站的路徑,然后再拼接文件的相對路徑即可:
看到?jīng)]:圖片中HttpContext.Current顯示的是 null ,所以您要是再調(diào)用MapPath,就必死無疑!
在此我也奉勸大家一句:盡量不要用MapPath,HttpRuntime.AppDomainAppPath才是更安全的選擇。
異步調(diào)用中如何訪問HttpContext?
前面我還提到在APM模式下的異步完成回調(diào)時,訪問HttpContext.Current也會返回null,那么此時該怎么辦呢?
答案有二種:
1. 在類型中添加一個字段來保存HttpContext的引用(異步開始前)。
2. 將HttpContext賦值給BeginXXX方法的最后一個參數(shù)(object state)
建議優(yōu)先選擇第二種方法,因為可以防止以后他人維護時數(shù)據(jù)成員被意外使用。
安全地使用HttpContext.Current
有時我們會寫些通用類庫給ASP.NET或者WindowsService程序來使用,例如異常記錄的工具方法。對于ASP.NET程序來說,我們肯定希望在異常發(fā)生時,能記錄URL,表單值,Cookie等等數(shù)據(jù),便于事后分析。然而對于WindowsService這類程序來說,您肯定沒想過要記錄Cookie吧?那么如何實現(xiàn)一個通用的功能呢?
方法其實也簡單,就是要判斷HttpContext.Current是否返回null,例如下面的示例代碼:
- public static void LogException(Exception ex)
- {
- StringBuilder sb = new StringBuilder();
- sb.Append("異常發(fā)生時間:").AppendLine(DateTime.Now.ToString());
- sb.AppendLine(ex.ToString());
- // 如果是ASP.NET程序,還需要記錄URL,F(xiàn)ORM, COOKIE之類的數(shù)據(jù)
- HttpContext context = HttpContext.Current;
- if( context != null ) {
- // 能運行到這里,就肯定是在處理ASP.NET請求,我們可以放心地訪問Request的所有數(shù)據(jù)
- sb.AppendLine("Url:" + context.Request.RawUrl);
- // 還有記錄什么數(shù)據(jù),您自己來實現(xiàn)吧。
- }
- System.IO.File.AppendAllText("日志文件路徑", sb.ToString());
- }
就是一個判斷,解決了所有問題,所以請忘記下面這類不安全的寫法吧:
- HttpContext.Current.Request.RawUrl;
- HttpContext.Current.Server.MapPath("xxxxxx");
下面的方法才是安全的:
- HttpContext context = HttpContext.Current;
- if( context != null ) {
- // 在這里訪問與請求有關(guān)的東西。
- }
當(dāng)前名稱:HttpContext.Current并非無處不在
文章源于:http://www.dlmjj.cn/article/dpidsog.html


咨詢
建站咨詢
