为ASP.NET MVC扩展异步Action功能(下)
本文分為上下兩部分,您也可以從《Extend ASP.NET MVC for Asynchronous Action》獲得全部內(nèi)容。
執(zhí)行Action方法
對(duì)于執(zhí)行同步Action的SyncMvcHandler,其實(shí)現(xiàn)十分簡(jiǎn)單而直接:
public class SyncMvcHandler : IHttpHandler, IRequiresSessionState {public SyncMvcHandler(IController controller,IControllerFactory controllerFactory,RequestContext requestContext){this.Controller = controller;this.ControllerFactory = controllerFactory;this.RequestContext = requestContext;}public IController Controller { get; private set; }public RequestContext RequestContext { get; private set; }public IControllerFactory ControllerFactory { get; private set; }public virtual bool IsReusable { get { return false; } }public virtual void ProcessRequest(HttpContext context){try{this.Controller.Execute(this.RequestContext);}finally{this.ControllerFactory.ReleaseController(this.Controller);}} }而對(duì)于異步Action,我之前一直思考著怎么將框架的默認(rèn)實(shí)現(xiàn),也就是單個(gè)方法調(diào)用,轉(zhuǎn)化成兩個(gè)方法(BeginXxx/EndXxx)調(diào)用。曾經(jīng)我想過自己實(shí)現(xiàn)一個(gè)新的ActionInvoker,但是這就涉及到了大量的工作,尤其是如果希望保持框架現(xiàn)有的功能(ActionFilter,ActionSelector等等),最省力的方法可能就是繼承ControllerActionInvoker,并設(shè)法使用框架已經(jīng)實(shí)現(xiàn)的各種輔助方法。但是在分析了框架代碼之后我發(fā)現(xiàn)復(fù)用也非常困難,舉例來說,ControllerActionInvoker判定一個(gè)方法為Action的依據(jù)之一是這個(gè)方法返回的是ActionResult類型或其子類,這意味著我無法直接使用這個(gè)方法來獲取一個(gè)返回IAsyncResult的BeginXxx方法;同理,對(duì)于查找EndXxx方法,我可能需要在請(qǐng)求名為Abc的異步Action時(shí),將EndAbc作為查找依據(jù)交由現(xiàn)成的方法來查詢——但是,如果又有一個(gè)請(qǐng)求是直接針對(duì)一個(gè)名為EndAbc的同步Action的那又怎么辦呢?
由于這些問題存在,我在去年設(shè)法實(shí)現(xiàn)異步Action時(shí)幾乎重寫了整個(gè)ActionInvoker——其復(fù)雜程度可見一斑。而且那個(gè)實(shí)現(xiàn)對(duì)于一些特殊情況的處理依舊不甚友好,需要開發(fā)人員在一定程度上做出妥協(xié)。這個(gè)實(shí)現(xiàn)在TechED 2008 China的Session中公布時(shí)我就承認(rèn)它并不能讓我滿意,建議大家不要將其投入生產(chǎn)環(huán)境中。而現(xiàn)在的實(shí)現(xiàn),則非常順利地解決了整個(gè)問題。雖然從理論上講還不夠“完美”,雖然還做出了一些讓步。
帶來如此多問題的原因就在于我們?cè)谠O(shè)法顛覆框架內(nèi)部的關(guān)鍵性設(shè)計(jì),也就是從單一的Action方法調(diào)用,轉(zhuǎn)變?yōu)椤胺螦PM的”二段式調(diào)用。等等,您是否感覺到了解決問題的關(guān)鍵?沒錯(cuò),那就是“符合APM的”。APM要求我們將一個(gè)行為分為BeginXxx和EndXxx兩個(gè)方法,可是既然ASP.NET MVC框架只能讓我們返回一個(gè)ActionResult對(duì)象……那么我們?yōu)槭裁床辉谶@個(gè)對(duì)象里包含方法的引用——也就是一個(gè)委托對(duì)象呢?這雖然不符合正統(tǒng)的APM簽名,但是完全可行,不是嗎?
public class AsyncActionResult : ActionResult {public AsyncActionResult(IAsyncResult asyncResult,Func<IAsyncResult, ActionResult> endDelegate){this.AsyncResult = asyncResult;this.EndDelegate = endDelegate;}public IAsyncResult AsyncResult { get; private set; }public Func<IAsyncResult, ActionResult> EndDelegate { get; private set; }public override void ExecuteResult(ControllerContext context){context.Controller.SetAsyncResult(this.AsyncResult).SetAsyncEndDelegate(this.EndDelegate);} }由于在Action方法中可以調(diào)用BeginXxx方法,我們?cè)贏syncActionResult中只需保留Begin方法返回的IAsyncResult,以及另一個(gè)對(duì)于EndXxx方法的引用。在AsyncActionResult的ExecuteResult方法中將會(huì)保存這兩個(gè)對(duì)象,以便在AsyncMvcHandler的EndProcessRequest方法中重新獲取并使用。根據(jù)“慣例”,我們還需要定義一個(gè)擴(kuò)展方法,方便開發(fā)人員在Action方法中返回一個(gè)AsyncActionResult。具體實(shí)現(xiàn)非常容易,在這里就展示一下異步Action的編寫方式:
[AsyncAction] public ActionResult AsyncAction(AsyncCallback asyncCallback, object asyncState) {SqlConnection conn = new SqlConnection("...;Asynchronous Processing=true");SqlCommand cmd = new SqlCommand("WAITFOR DELAY '00:00:03';", conn);conn.Open();return this.Async(cmd.BeginExecuteNonQuery(asyncCallback, asyncState),(ar) =>{int value = cmd.EndExecuteNonQuery(ar);conn.Close();return this.View();}); }至此,似乎AsyncMvcHandler也無甚秘密可言了:
public class AsyncMvcHandler : IHttpAsyncHandler, IRequiresSessionState {public AsyncMvcHandler(Controller controller,IControllerFactory controllerFactory,RequestContext requestContext){this.Controller = controller;this.ControllerFactory = controllerFactory;this.RequestContext = requestContext;}public Controller Controller { get; private set; }public RequestContext RequestContext { get; private set; }public IControllerFactory ControllerFactory { get; private set; }public HttpContext Context { get; private set; }public IAsyncResult BeginProcessRequest(HttpContext context,AsyncCallback cb,object extraData){this.Context = context;this.Controller.SetAsyncCallback(cb).SetAsyncState(extraData);try{(this.Controller as IController).Execute(this.RequestContext);return this.Controller.GetAsyncResult();}catch{this.ControllerFactory.ReleaseController(this.Controller);throw;}}public void EndProcessRequest(IAsyncResult result){try{HttpContext.Current = this.Context;ActionResult actionResult = this.Controller.GetAsyncEndDelegate()(result);if (actionResult != null){actionResult.ExecuteResult(this.Controller.ControllerContext);}}finally{this.ControllerFactory.ReleaseController(this.Controller);}} }在BeginProcessRequest方法中將保存當(dāng)前Context——這點(diǎn)很重要,HttpContext.Current是基于CallContext的,一旦經(jīng)過一次異步回調(diào)HttpContext.Current就變成了null,我們必須重設(shè)。接著將接收到的AsyncCallback和AsyncState保留,并使用框架中現(xiàn)成的Execute方法執(zhí)行控制器。當(dāng)Execute方法返回時(shí)一整個(gè)Action方法的調(diào)用流程已經(jīng)結(jié)束,這意味著其調(diào)用結(jié)果——即IAsyncResult和EndDelegate對(duì)象已經(jīng)保留。于是將IAsyncResult對(duì)象取出并返回。至于EndProcessRequest方法,只是將BeginProcessRequest方法中保存下來的EndDelegate取出,調(diào)用,把得到的ActionResult再執(zhí)行一遍即可。
以上的代碼只涉及到普通情況下的邏輯,而在完整的代碼中還會(huì)包括對(duì)于Action方法被某個(gè)Filter終止或替換等特殊情況下的處理。此外,無論在BeginProcessRequest還是EndProcessRequest中都需要對(duì)異常進(jìn)行合適地處理,使得Controller Factory能夠及時(shí)地對(duì)Controller對(duì)象進(jìn)行釋放。
ModelBinder支持
其實(shí)您到目前為止還不能使用異步Action,因?yàn)槟鷷?huì)發(fā)現(xiàn)方法的AsyncCallback參數(shù)得到的永遠(yuǎn)是null。這是因?yàn)槟J(rèn)的Model Binder無法得知如何從一個(gè)上下文環(huán)境中得到一個(gè)AsyncCallback對(duì)象。這一點(diǎn)倒非常簡(jiǎn)單,我們只需要構(gòu)造一個(gè)AsyncCallbackModelBinder,而它的BindModel方法僅僅是將AsyncMvcHandler.BeginProcessRequest方法中保存的AsyncCallback對(duì)象取出并返回:
public sealed class AsyncCallbackModelBinder : IModelBinder {public object BindModel(ControllerContext controllerContext,ModelBindingContext bindingContext){return controllerContext.Controller.GetAsyncCallback();} }其使用方式,便是在應(yīng)用程序啟動(dòng)時(shí)將其注冊(cè)為AsyncCallback類型的默認(rèn)Binder:
protected void Application_Start() {RegisterRoutes(RouteTable.Routes);ModelBinders.Binders[typeof(AsyncCallback)] = new AsyncCallbackModelBinder(); }對(duì)于asyncState參數(shù)您也可以使用類似的做法,不過這似乎有些不妥,因?yàn)閛bject類型實(shí)在過于寬泛,并不能明確代指asyncState參數(shù)。事實(shí)上,即使您不為asyncState設(shè)置binder也沒有太大問題,因?yàn)閷?duì)于一個(gè)異步ASP.NET請(qǐng)求來說,其asyncState永遠(yuǎn)是null。如果您一定要指定一個(gè)binder,我建議您在每個(gè)Action方法的asyncState參數(shù)上標(biāo)記如下的Attribute,它和AsyncStateModelBinder也已經(jīng)被一并建入項(xiàng)目中了:
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] public sealed class AsyncStateAttribute : CustomModelBinderAttribute {private static AsyncStateModelBinder s_modelBinder = new AsyncStateModelBinder();public override IModelBinder GetBinder(){return s_modelBinder;} }使用方式如下:
[AsyncAction] public ActionResult AsyncAction(AsyncCallback cb, [AsyncState]object state) { ... }其實(shí),基于Controller的擴(kuò)展方法GetAsyncCallback和GetAsyncState均為公有方法,您也可以讓Action方法不接受這兩個(gè)參數(shù)而直接從Controller中獲取——當(dāng)然這種做法降低了可測(cè)試性,不值得提倡。
限制和缺點(diǎn)
如果這個(gè)解決方案沒有缺陷,那么相信它已經(jīng)被放入ASP.NET MVC 1.0中,而輪不到我在這里擴(kuò)展一番了。目前的這個(gè)解決方案至少有以下幾點(diǎn)不足:
根據(jù)ASP.NET MVC框架的Roadmap,ASP.NET MVC框架1.0之后的版本中將會(huì)支持異步Action,相信以上這些缺陷到時(shí)候都能被彌補(bǔ)。不過這就需要大量的工作,這只能交給ASP.NET MVC團(tuán)隊(duì)去慢慢執(zhí)行了。事實(shí)上,您現(xiàn)在已經(jīng)可以在ASP.NET MVC RC源代碼的MvcFutures項(xiàng)目中找到異步Action處理的相關(guān)內(nèi)容。它添加了IAsyncController,AsyncController,IAsyncActionInvoker,AsyncControllerActionInvoker等許多擴(kuò)展。雖說它們都“繼承”了現(xiàn)有的類,但是與我之前的判斷相似,如AsyncControllerActionInvoker幾乎完全重新實(shí)現(xiàn)了一遍ActionInvoker中的各種功能——我還沒有仔細(xì)閱讀代碼,因此無法判斷出這種設(shè)計(jì)是否優(yōu)秀,只希望它能像ASP.NET MVC本身那樣的簡(jiǎn)單和優(yōu)雅。
接下來,我打算為現(xiàn)在的代碼的EndXxx方法也加上Filter支持,我需要仔細(xì)閱讀ASP.NET MVC的源代碼來尋找解決方案。希望它能夠成為ASP.NET MVC正式支持異步Action之前較好的替代方案。
更多資料
完整的項(xiàng)目代碼已經(jīng)放置在MSDN Code Gallery中,您可以在這里訪問到關(guān)于它的“性能測(cè)試”等更多信息。這篇文章著重講解了擴(kuò)展的設(shè)計(jì)原理,省略了涉及特殊狀況處理以及程序健壯性等實(shí)現(xiàn)細(xì)節(jié)的描述,歡迎您下載代碼并提出改進(jìn)建議。
?
上一篇:為ASP.NET MVC擴(kuò)展異步Action功能(上)
轉(zhuǎn)載于:https://www.cnblogs.com/JeffreyZhao/archive/2009/02/04/extend-asp-net-mvc-for-asynchronous-action-2.html
總結(jié)
以上是生活随笔為你收集整理的为ASP.NET MVC扩展异步Action功能(下)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 中文字号转换成英文的字号
- 下一篇: 分享几个简单的WPF控件(代码)