ASP.NET 4.0 的 HTML 編碼語法,和背後的故事

在 .NET 2.0,要將 .aspx 網頁中顯示的字串編碼時,要這樣寫:

    <%= HttpUtility.HtmlEncode(str) %>

到了 ASP.NET MVC 1.0,可以用 HtmlHelper 類別的 Encode 方法:

    <%= Html.Encode(ViewData["Message"]) %>

其中的 Html 就是 HtmlHelper 類別的 instance。這跟之前的寫法沒有太大差異。

現在,ASP.NET 4.0 有更簡潔 <%: %> 語法可以達到同樣的效果,例如:

    <%: ViewData["Message"] %>

為什麼要編碼?

將顯示於網頁上的字串編碼,可以減少網站受到 XSS(cross-site scripting)攻擊的機會,比如說,如果有人故意在網站的留言版上輸入以下內容:

  <script>alert("Hello!");</script>

在沒有特別處理的情況下,網頁程式可能只是單純將使用者的留言直接以原字串輸出,導致瀏覽器執行其中的 JavaScript 指令。

有興趣的話,可以用 Visual Studio 2010 寫個簡單的 ASP.NET MVC 應用程式看看,步驟如下:
  1. File > New > Project,專案範本選擇 Visual C# 的 ASP.NET MVC 2 Web Application,專案名稱輸入 MyMvcApp。
  2. 專案建立完成後,在 Solution Explorer 視窗中找到專案底下的 Controllers\HomeController.cs,修改此類別的 Index() 方法:

    public ActionResult Index()
    {
      ViewData[
    "Message"] = "<script>alert('Hello!');</script>";
     
    return View();
    }

  3. 在執行之前,看一眼 Views\Home\Index.aspx,確認網頁是使用 <%: %> 語法來顯示 ViewData 的內容:

    <%: ViewData["Message"%>

  4. 執行看看(Debug > Start Debugging 或 F5)。結果如下圖,JavaScript 指令碼並不會被執行,而是顯示成一般的文字:



  5. 關閉此瀏覽器視窗,然後修改 Views\Home\Index.aspx,把 <%: %> 語法改成 <%= %>,再執行看看,這次應該會彈出 alert 對話窗:
所以,在 ASP.NET 4.0,我們可以盡量使用簡潔的 <%: %> 語法來輸出網頁內容,以降低 XSS 攻擊的風險。

告訴 <%: %> 不要編碼

雖然將輸出至網頁的內容編碼是個好習慣,但有些情況是不要編碼的,例如我們可能在伺服器端的程式中就已經有編碼了,不希望它再重複編碼;或者輸出至網頁的字串本身就包含 tag 字元,像這樣:

  public ActionResult Index()
  {
    ViewData["Message"] = "Hello! <BR /> MVC world!";
    return View();
  }


如果在對應的 Index.aspx 裡面用 <%: ViewData["Message"] %> 將字串編碼,結果網頁上就會直接顯示 "<BR />",而不是我們希望看到的換行效果。碰到這種情況,當然可以修改 .aspx 頁面,改用原本的 <%= %> 來輸出字串。另一種方法,我們仍可以在 .aspx 裡面使用 <%: %>,並且在 Controller 類別裡面動手腳,讓 <%: %> 忽略編碼的動作。參考以下程式片段:

  public ActionResult Index()
  {
    ViewData["Message"] = MvcHtmlString.Create("Hello!
<BR /> MVC world!");
    return View();
  }


執行結果如下圖:


這裡是使用 MvcHtmlString 類別的 Create 方法,將我們要顯示的字串包在一個 MvcHtmlString 物件裡。而當 <%: %> 看到傳入的物件是 MvcHtmlString 時,它就不會編碼,而是直接將字串內容輸出至網頁。

再挖深一點

比較精確的說法應該是:當<%: %> 看到傳入的物件有實作 IHtmlString 介面時,就會略過編碼的動作。

這也是為什麼當我們在 .aspx 頁面中使用類似

<%: Html.TextBox("TextBox1""test"%> 

的寫法時,可以正 確產生 HTML 元素、而不會在網頁上顯示 <input id="TextBox1" ... /> 的原因。
如果往 ASP.NET MVC 2 的原始碼裡面追(註1),可以發現 MvcHtmlString.cs 裡面有這麼一段:

  Type iHtmlStringType = typeof(HttpContext).Assembly.GetType("System.Web.IHtmlString");
  if (iHtmlStringType != null) {
      // first, create the dynamic type
      Type dynamicType = DynamicTypeGenerator.GenerateType(
          "DynamicMvcHtmlString"typeof(MvcHtmlString),
          new
 Type[] { iHtmlStringType });

      ....(略)
  }


原來,當我們呼叫 MvcHtmlString.Create 方法時,它會利用 DynamicTypeGenerator 類別來動態建立一個新型別:DynamicMvcHtmlString,並且讓這個型別實作 IHtmlString 介面。然後就如前面說的,<%: %> 看到我們傳給它的物件有實作 IHtmlString,就會略過編碼的動作,而直接呼叫該物件的 ToHtmlString() 方法,並將取得的字串輸出至網頁。

既然關鍵在 IHtmlString 介面,那就表示我們也可以不用 MvcHtmlString,而改用自己設計的類別囉(只要它有實作 IHtmlString)。這個小實驗的步驟就不細說了,直接看圖吧:



註1:在單步除錯時,若要追進 .NET 原始碼,可以更改 Visual Studio 2010 的設定,這個設定是在 Tools > Options > Debugging > Enable .NET Framework source stepping。這裡有圖文並茂的說明:Setting up Visual Studio 2010 to step into Microsoft .NET Source Code。值得注意的是,將此選項打勾會一併取消「Enable Just My Code」選項,也就是說,不僅 .NET 原始碼,除錯時碰到任何「不是我的程式碼」,按 F11(step into)都會令 Visual Studio 嘗試追進去。至於何謂「我的程式碼」,可參考官方文件<HOW TO:逐步執行 Just My Code>。
Copyright © 2012. Huan-Lin 學習筆記 - All Rights Reserved
Powered by Blogger
Template Design by Cool Blogger Tutorials
Published by Templates Doctor