[Visual Studio "11" Series] ASP.NET 4.5 新功能 (2): 核心部份的新功能

汗顏啊 ... 距上一篇 ASP.NET 4.5 新功能 (1) 發文日期已經整整五個月有了,這五個月實在是大小事太多,又被 ASP.NET MVC 3 拉過去了,一直沒有繼續補下去,昨晚 Visual Studio "11" 正式發出 beta 版本,所以本系列文也就再復活了 XD。

汗顏啊 ... 距上一篇 ASP.NET 4.5 新功能 (1) 發文日期已經整整五個月有了,這五個月實在是大小事太多,又被 ASP.NET MVC 3 拉過去了,一直沒有繼續補下去,昨晚 Visual Studio "11" 正式發出 beta 版本,所以本系列文也就再復活了 XD。

ASP.NET 4.5 在 Web Form 上著重的其實大多是在新規格的 HTML 5 和將 ASP.NET MVC 上具特色的功能移植到 Web Form (Dynamic Data 的 Model Binding 以及 Client-side 強化認證機制就是如此),不過本篇要講的是在核心層 (Core Service) 的新功能,也就是 Web Form 和 MVC 都適用,所以不管你是寫 Web Form 還是 MVC,都要稍微關注一下這裡的變化。

 

1. 非同步機制的支援

.NET Framework 4.5 中最重要的更新莫過於非同步開發方式的改變,像是 await 運算子以及 Task-based (async) 非同步模型。

await 比較像是一個自動將 Async 和 Completed 非同步模型自動包裝起來的修飾子 (modifier),透過這個修飾子處理的非同步作業,不用再自己處理 Begin/End 和 Async/Completed 工作,這些工作會由編譯器魔法 (compiler magic) 做掉,但它本質上並不會封鎖發起非同步的 thread,就像 auto-implement property 與 foreach 的 state machine 一樣是由編譯器產生的,所以開發人員可以不用去管 await 做了什麼事,只不過如果在非同步期間還要做其他事 (ex: 更新使用者介面) 的話,可能就要自己做了。想了解更多關於 await 的資訊,可參考:http://blogs.msdn.com/b/ericlippert/archive/2010/10/29/asynchronous-programming-in-c-5-0-part-two-whence-await.aspx

async 則是一個以 Task 為主的非同步模型,它不像 await 可用在方法呼叫,它只能用在方法修飾上,它的意思是讓程式在執行到這個方法時,會自動生成一個 thread 以非同步方式去執行它,這個也是編譯器魔術的一種,而 async 可以和 await 並用 (方法宣告時使用 async,而方法本體中使用 await)。想了解更多的話,請參考:http://msdn.microsoft.com/en-us/library/hh156513(v=vs.110).aspx

而 ASP.NET 4.5 針對 async 和 await 的支援,基本上是以在 ASP.NET 上可完整運用 async 和 await 為主,為因應這樣的需求,ASP.NET 4.5 中新增了一個 EventHandlerTaskAsyncHelper 物件,它可以將被 async 宣告的方法包裝起來,然後交給原本要處理的 HttpApplication 的非同步相關函式處理 (ex: AddOnPostAuthorizeRequestAsync),後續再由 HttpApplication 來處理即可,適合在非同步的 HTTP Module 使用。例如:

private async Task
ScrapeHtmlPage(object caller, EventArgs e)
 {
    WebClient wc = new WebClient();
    var result = await wc.DownloadStringTaskAsync("http://www.microsoft.com");
    // Do something with the result
}
public void Init(HttpApplication
context)
 {
   // Wrap the Task-based method so that it can be used with 
   // the older async programming model.
   EventHandlerTaskAsyncHelper helper = 
        new EventHandlerTaskAsyncHelper(ScrapeHtmlPage);
 
        // The helper object makes it easy to extract Begin/End methods out of
        // a method that returns a Task object. The ASP.NET pipeline calls the 
        // Begin and End methods to start and complete calls on asynchronous 
        // HTTP modules.
        context.AddOnPostAuthorizeRequestAsync(
            helper.BeginEventHandler, helper.EndEventHandler);
    }

 

至於 HTTP Handler,要先將 HttpHandler 宣告為非同步 (利用 ASP.NET 4.5 新增的 HttpTaskAsyncHandler 類別),再覆寫其 ProcessRequestAsync() 方法,這個方法是使用 async 宣告的方法,因應前面所講的,ASP.NET 4.5 會針對 HttpTaskAsyncHandler 自動註冊 EventHandlerTaskAsyncHelper,所以 ASP.NET 4.5 內部可以直接處理 Begin/End 的作業,只要開發人員適當的使用 await 即可。

public class MyAsyncHandler : HttpTaskAsyncHandler
{
    // ...
 
    // ASP.NET automatically takes care of integrating the Task based override
    // with the ASP.NET pipeline.
    public override async Task ProcessRequestAsync(HttpContext context)
    {
        WebClient wc = new WebClient();
        var result = await 
            wc.DownloadStringTaskAsync("http://www.microsoft.com");
        // Do something with the result
    }
}

 

2. 要求驗證 (request validation) 功能

要求驗證是大家常使用到的內建功能,也是為了要讓 ASP.NET 的來回更安全所做的一種保護措施,在 ASP.NET 3.5 前,在資料進入 ASP.NET 核心層前都要被核心層檢驗,以防止資料被竄改,不過這會影響到一些效能,而且如果資料沒有被程式使用的話,額外的驗證反而變成多餘的,所以 ASP.NET 4.5 引入了延遲驗證 (deferred request validation/lazy request validation) 的機制,當 ASP.NET 發現 Web.config 中的 requestValidationMode="4.5" 時,就會啟用這個功能,它只會在程式即將使用資料時才會進行驗證,而且只會驗證程式要取用的資料,不是所有資料都驗證。例如程式使用 Request.Form["myfield"] 取得 POST 資料時,程式只會針對 myfield 這個 POST 欄位做驗證,其他的保持不處理,等待需要時才驗證。

然而,如果欄位中有使用到 HTML 字串 (HTML 編輯器),那麼這個功能仍然沒有辦法解決驗證問題,ASP.NET 4.5 另外提供了一個 Request.Unvalidated 屬性,它一樣會在 requestValidationMode="4.5" 時才生效,它會放置最原始未經驗證的資料,開發人員可以利用這個屬性來取得最原始的表單資料,以略過要求驗證的流程。Request.Unvalidated 屬性支援 QueryString, Cookie, Form, Url 等資料類型。不過 Request.Unvalidated 只是給開發人員略過內建的要求驗證,而開發人員仍要自己實作驗證邏輯,以免被惡意攻擊。

 

3. AntiXSS 成為內建功能

AntiXSS 這個強大的 XSS 攻擊保護函式庫,在 ASP.NET 上一直屬於外掛的功能,但到了 ASP.NET 4.5,它變成了內建的功能,ASP.NET 4.5 內建的是 AntiXSS 4.0,命名空間是 System.Web.Security.AntiXSS,內含了 AntiXSSEncoder 類別,但是它預設並不會啟用,必須要開發人員在 Web.config 中加入這一段:

<httpRuntime ...
  encoderType="System.Web.Security.AntiXss.AntiXssEncoder,
                          System.Web, 
                         Version=4.0.0.0, 
                         Culture=neutral, 
                         PublicKeyToken=b03f5f7f11d50a3a" />

而像是 HtmlEncode, HtmlFormUrlEncode, XmlEncode, UrlEncode, UrlPathEncode, CssEncode 等也都成為了內建功能,為防護 XSS 攻擊做了更完善的保護。

 

4. WebSockets 的支援

Websockets 是 HTML 5 最令人期待的新功能之一,它允許 JavaScript 直接在 HTML 網頁上實作傳輸二進位資料的功能,可以說是 XMLHttpRequest 的升級版,而在即將推出的 Windows Server "8" 中的 IIS 8 也開始支援 WebSockets 的功能,當然也不會缺少 ASP.NET 這塊,所以 ASP.NET 核心層的 HttpContext 中加入了一個新成員:AcceptWebSocketRequest(),它允許 ASP.NET 開發人員特別針對 WebSocket 撰寫處理常式,以持續供應 WebSocket 的要求,例如:

public async Task MyWebSocket(AspNetWebSocketContext context)
 {
    WebSocket socket = context.WebSocket;
    while (true)
    {
        ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[1024]);
 
        // Asynchronously wait for a message to arrive from a client
        WebSocketReceiveResult result = 
            await socket.ReceiveAsync(buffer, CancellationToken.None);
 
        // If the socket is still open, echo the message back to the client
        if (socket.State == WebSocketState.Open)
        {
            string userMessage = Encoding.UTF8.GetString(buffer.Array, 0,
                result.Count);
            userMessage = "You sent: " + userMessage + " at " + 
                DateTime.Now.ToLongTimeString();
            buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(userMessage));
 
            // Asynchronously send a message to the client
            await socket.SendAsync(buffer, WebSocketMessageType.Text,
                true, CancellationToken.None);
        }
        else { break; }
    }
}

 

基本上只要把 WebSockets 的要求當做一般的 socket 或 TcpClient 來看,其網路傳輸的方式大同小異,一樣是要檢查連線的存在性,以及持續的傳輸資料等等。

 

5. 打包 (Bundling) 與壓縮 (Minification) 功能

打包功能是 ASP.NET 4.5 的新功能,它可以允許開發人員將所有的 JavaScript 或 CSS 全部打包,瀏覽器只要透過一個 Request 就能得到所有必要的 JavaScript 和 CSS,例如網站中的 JavaScript 和 CSS 的佈局如下:

而開發人員只需要在網頁中使用 "scripts/js" 和 "styles/css" 就可以取得所有的打包資料了。以預設的打包演算法,"js" 會打包所有副檔名為 .js 的檔案,"css" 則是打包副檔名為 .css 的檔案,而檔案的排序會以檔名為主。當然,這個演算法是可以客制的,例如:

// Creates a new bundle located at the URL "[host]/customscript".
// The bundle uses the built in JsMinify class to perform minification
var b = new Bundle("~/CustomScript", new JsMinify());
// Adds all .js files in the Scripts folder. 
// Does not include sub-folders by default.
 b.AddDirectory("~/scripts", "*.js");
// Add any text based file relative to the project root
b.AddFile("~/scripts/script.js");
BundleTable.Bundles.Add(b);

這樣開發人員就可以直接用 "/CustomScript" 來代表自己的打包演算法流程,進行打包的工作。

另外,在打包過程中,預設會啟用壓縮的功能,例如前列程式碼中的 JsMinify 物件,它會壓縮 JavaScript 指令碼,而 CssMinify 物件則是會壓縮 CSS 檔案,這兩個類別都在 System.Web.Optimization 命名空間 (ASP.NET 4.5 新增) 中。

 

6. 核心層的資料處理強化

如果經常要處理來自用戶端的要求時,若不想要由 ASP.NET 來處理,則開發人員使用的方法應該都是 Request.InputStream,不過它會要求在整個讀取完成時,才回傳 Stream,在 ASP.NET 4.0 時,HttpRequest 增加了一個 GetBufferlessInputStream() 方法,它不會要求整個資料流讀完後才回傳,所以開發人員可以更快的取得 Stream,只是它又衍生了一個問題,就是如果用戶端再次要求時,GetBufferlessInputStream() 會抓不到原先的 HTTP 要求資料,導致 Web Form 或 MVC View 失效,這個問題更常見於非同步作業,因此 ASP.NET 4.5 新增了一個 GetBufferedInputStream(),它一樣會回傳 Stream,只不過它在資料進來的時候會多複製一份副本到 buffer 中,而用戶端實際的 Stream 不再是來自 raw-stream,而是來自 buffered stream,後面的 Handler 在處理時就不會再因為掉資料而發生錯誤的問題了。

另外,在輸出 HttpResponse 時,我們常會下 Flush() 強制輸出,不過若是在低頻寬以及長時工作 (Long-term processing) 時,Flush() 會有一些異常的狀況,因為它是同步作業,所以在 ASP.NET 4.5 中,HttpResponse 多了 BeginFlush() 與 EndFlush() 方法,讓 Flush 工作非同步化,也可以因為呼叫 Flush() 次數降低,省下額外因為 Flush() 所產生的 thread 數以及支援非同步的處理工作。

 

還有一個針對 Web Hosting 才有的新功能,但這裡我就不羅列了,有興趣的讀者可直接到這裡參考:http://www.asp.net/vnext/overview/whitepapers/whats-new#_Toc_perf

 

Reference:

What's New in ASP.NET 4.5: http://www.asp.net/vnext/overview/whitepapers/whats-new