用ASP.NET MVC源代碼尋找解決方案
ASP.NET MVC源代碼來尋找解決方案,由于在Action方法中可以調用BeginXxx方法,我們在AsyncActionResult中只需保留Begin方法返回的IAsyncResult,以及另一個對于EndXxx方法的引用。在AsyncActionResult的ExecuteResult方法中將會保存這兩個對象,以便在AsyncMvcHandler的EndProcessRequest方法中重新獲取并使用。根據“慣例”,我們還需要定義一個擴展方法,方便開發人員在Action方法中返回一個AsyncActionResult。具體實現非常容易,在這里就展示一下異步Action的編寫方式:
- [AsyncAction]
- publicActionResultAsyncAction(AsyncCallbackasyncCallback,objectasyncState)
- {
- SqlConnectionconn=newSqlConnection("...;AsynchronousProcessing=true");
- SqlCommandcmd=newSqlCommand("WAITFORDELAY'00:00:03';",conn);
- conn.Open();
- returnthis.Async(
- cmd.BeginExecuteNonQuery(asyncCallback,asyncState),
- (ar)=>
- {
- intvalue=cmd.EndExecuteNonQuery(ar);
- conn.Close();
- returnthis.View();
- });
- }
至此,似乎AsyncMvcHandler也無甚秘密可言了:
- publicclassAsyncMvcHandler:IHttpAsyncHandler,IRequiresSessionState
- {
- publicAsyncMvcHandler(
- Controllercontroller,
- IControllerFactorycontrollerFactory,
- RequestContextrequestContext)
- {
- this.Controller=controller;
- this.ControllerFactory=controllerFactory;
- this.RequestContext=requestContext;
- }
- publicControllerController{get;privateset;}
- publicRequestContextRequestContext{get;privateset;}
- publicIControllerFactoryControllerFactory{get;privateset;}
- publicHttpContextContext{get;privateset;}
- publicIAsyncResultBeginProcessRequest(
- HttpContextcontext,
- AsyncCallbackcb,
- objectextraData)
- {
- this.Context=context;
- this.Controller.SetAsyncCallback(cb).SetAsyncState(extraData);
- try
- {
- (this.ControllerasIController).Execute(this.RequestContext);
- returnthis.Controller.GetAsyncResult();
- }
- catch
- {
- this.ControllerFactory.ReleaseController(this.Controller);
- throw;
- }
- }
- publicvoidEndProcessRequest(IAsyncResultresult)
- {
- try
- {
- HttpContext.Current=this.Context;
- ActionResultactionResult=this.Controller.GetAsyncEndDelegate()(result);
- if(actionResult!=null)
- {
- actionResult.ExecuteResult(this.Controller.ControllerContext);
- }
- }
- finally
- {
- this.ControllerFactory.ReleaseController(this.Controller);
- }
- }
- }
在BeginProcessRequest方法中將保存當前Context——這點很重要,HttpContext.Current是基于 CallContext的,一旦經過一次異步回調HttpContext.Current就變成了null,我們必須重設。接著將接收到的 AsyncCallback和AsyncState保留,并使用框架中現成的Execute方法執行控制器。當Execute方法返回時一整個Action方法的調用流程已經結束,這意味著其調用結果——即IAsyncResult和EndDelegate對象已經保留。于是將IAsyncResult對象取出并返回。至于EndProcessRequest方法,只是將BeginProcessRequest方法中保存下來的EndDelegate取出,調用,把得到的ActionResult再執行一遍即可。
以上的代碼只涉及到普通情況下的邏輯,而在完整的代碼中還會包括對于Action方法被某個Filter終止或替換等特殊情況下的處理。此外,無論在BeginProcessRequest還是EndProcessRequest中都需要對異常進行合適地處理,使得Controller Factory能夠及時地對Controller對象進行釋放。
如果這個解決方案沒有缺陷,那么相信它已經被放入ASP.NET MVC 1.0中,而輪不到我在這里擴展一番了。目前的這個解決方案至少有以下幾點不足:
沒有嚴格遵守.NET中的APM模式,雖然不影響功能,但這始終是一個遺憾。
由于利用了框架中的現成功能,所有的Filter只能運行在BeginXxx方法上。
由于EndXxx方法和最終ActionResult的執行都沒有Filter支持,因此如果在這個過程中拋出了異常,將無法進入ASP.NET MVC建議的異常處理功能中。
根據ASP.NET MVC框架的Roadmap,ASP.NET MVC框架1.0之后的版本中將會支持異步Action,相信以上這些缺陷到時候都能被彌補。不過這就需要大量的工作,這只能交給ASP.NET MVC團隊去慢慢執行了。事實上,您現在已經可以在ASP.NET MVC源代碼的MvcFutures項目中找到異步Action處理的相關內容。它添加了 IAsyncController,AsyncController,IAsyncActionInvoker,AsyncControllerActionInvoker 等許多擴展。雖說它們都“繼承”了現有的類,但是與我之前的判斷相似,如AsyncControllerActionInvoker幾乎完全重新實現了一遍ActionInvoker中的各種功能——我還沒有仔細閱讀代碼,因此無法判斷出這種設計是否優秀,只希望它能像ASP.NET MVC本身那樣的簡單和優雅。
我打算為現在的代碼的EndXxx方法也加上Filter支持,我需要仔細閱讀ASP.NET MVC源代碼來尋找解決方案。希望它能夠成為ASP.NET MVC正式支持異步Action之前較好的替代方案。
【編輯推薦】