HttpContext.Current並非無處不在

來源:互聯網
上載者:User

瞭解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 ) {    // 在這裡訪問與請求有關的東西。}

如果,您認為閱讀這篇部落格讓您有些收穫,不妨點擊一下右下角的【推薦】按鈕。
如果,您希望更容易地發現我的新部落格,不妨點擊一下右下角的【關注 Fish Li】。
因為,我的寫作熱情也離不開您的肯定支援。

感謝您的閱讀,如果您對我的部落格所講述的內容有興趣,請繼續關注我的後續部落格,我是Fish Li 。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.