詳解WebFormView中標(biāo)準(zhǔn)修改辦法
對于WebFormView的標(biāo)準(zhǔn)修改辦法以及MvcPatch項目,這些東西的好處是如果我們在構(gòu)建一個面向AJAX請求的Action,此時View的內(nèi)容可能只是輸出的一部分,甚至我們要對內(nèi)容進(jìn)行過濾/編碼等額外操作。
在淺析WebFormView中的一個Bug中我提到WebFormView的實現(xiàn)破壞了IView對象設(shè)計思路,它會把視圖內(nèi)容直接生成至HttpContext.Current而不是Render方法指定的TextWriter中。目前,WebFormView.Render的調(diào)用方只有兩個:ViewResult.ExecuteResult方法還有HtmlHelper.RenderPartial方法,但是這兩者原本的目的地就是當(dāng)前的HttpContext,因此在平時使用時WebFormView的錯誤實現(xiàn)并不會造成問題。
- public static class HtmlExtensions
- {
- public static string Partial(this HtmlHelper htmlHelper, string partial)
- {
- var viewInstance = BuildManager.CreateInstanceFromVirtualPath(partial, typeof(object));
- var control = viewInstance as ViewUserControl;
- control.ViewContext = htmlHelper.ViewContext;
- control.ViewData = htmlHelper.ViewData;
- Page page = new ViewPage();
- page.Controls.Add(control);
- TextWriter writer = new StringWriter();
- htmlHelper.ViewContext.HttpContext.Server.Execute(page, writer, false);
- return writer.ToString();
- }
- }
但是,如果我們在構(gòu)建一個面向AJAX請求的Action,此時View的內(nèi)容可能只是輸出的一部分,甚至我們要對內(nèi)容進(jìn)行過濾/編碼等額外操作。此時,我們就希望指定一個TextWriter用于收集內(nèi)容——但是WebFormView自然無法做到。之前我提出了一種非常臨時,非常山寨,非常簡陋,繞彎,但是可行,或者說是可以“表現(xiàn)出解決問題的方法”的代碼,修改一下便能說明問題:
這個HtmlHelper的擴(kuò)展方法Partial,和HtmlHelper自帶的RenderPartial功能比較接近,不過Partial是將視圖內(nèi)容直接生成一個字符串并返回,RenderPartial方法是直接輸出至當(dāng)前HttpContext。因此它們在視圖中的使用方式是不同的:
<% Html.RenderPartial("MyPartialView"); %>
<%= Html.Partial("MyPartialView") %>RenderPartial以<%開頭,末尾有分號。而Partial以<%=開頭,末尾沒有分號。關(guān)于視圖中的各種輸出方式,我最近在閱讀ASP.NET源代碼時有更深的了解,下次我們再詳談。不過目前,我們還是專注于WebFormView的修改。
WebFormView目前問題的主要原因,是ViewPage和ViewUserControl兩個類中缺乏合適接口的原因:
- public class ViewPage : Page, IViewDataContainer {
- ...
- public virtual void RenderView(ViewContext viewContext) {
- ViewContext = viewContext;
- InitHelpers();
- // Tracing requires Page IDs to be unique.
- ID = Guid.NewGuid().ToString();
- ProcessRequest(HttpContext.Current);
- }
- }
- public class ViewUserControl : UserControl, IViewDataContainer {
- ...
- public virtual void RenderView(ViewContext viewContext) {
- viewContext.HttpContext.Response.Cache.SetExpires(DateTime.Now);
- // 這是ViewPage的子類,專用于生成獨立的ViewUserControl內(nèi)容
- var containerPage = new ViewUserControlContainerPage(this);
- // Tracing requires Page IDs to be unique.
- ID = Guid.NewGuid().ToString();
- // 其中會執(zhí)行ViewUserControlContrainerPage的RenderView方法
- RenderViewAndRestoreContentType(containerPage, viewContext);
- }
- }
可見,在ViewPage和ViewUserControl中各有一個RenderView方法,它們只包含一個ViewContext參數(shù),但是卻沒有輸出目的地。因此,最終它們使用HttpContext.Current這個邪惡的、臭名昭著的靜態(tài)屬性來生成內(nèi)容。現(xiàn)在想起來,我當(dāng)時在搞異步Action時,遭遇異常而不得不手動保持HttpContext就是這個原因造成的。于是我們目前修改的方式,便是為ViewPage和ViewUserControl增加一個額外的TextWriter參數(shù):
- public class ViewPage : Page, IViewDataContainer {
- ...
- public virtual void RenderView(ViewContext viewContext, ViewContext writer) {
- ViewContext = viewContext;
- InitHelpers();
- // Tracing requires Page IDs to be unique.
- ID = Guid.NewGuid().ToString();
- ProcessRequest(HttpContext.Current);
- viewContext.HttpContext.Server.Execute(this, writer, false);
- }
- }
- public class ViewUserControl : UserControl, IViewDataContainer {
- ...
- public virtual void RenderView(ViewContext viewContext, ViewContext writer) {
- viewContext.HttpContext.Response.Cache.SetExpires(DateTime.Now);
- var containerPage = new ViewUserControlContainerPage(this);
- // Tracing requires Page IDs to be unique.
- ID = Guid.NewGuid().ToString();
- RenderViewAndRestoreContentType(containerPage, viewContext, writer);
- }
- }
至于其他“順其自然”的修改就不值一提了。
在我看來,這種問題可能不是ASP.NET MVC的設(shè)計問題(Design Issue),但是這也是它的內(nèi)部實現(xiàn)的低級錯誤。對于此類問題,如果使用擴(kuò)展的方式進(jìn)行修改會顯得沉重而麻煩,需要各種擴(kuò)展和配置才能使用。之前項目中使用的便是基于“外部擴(kuò)展”來回避“內(nèi)部錯誤”的辦法,而目前已經(jīng)換成自行修改編譯過的System.Web.Mvc.dll了。這個修改版本目前已經(jīng)發(fā)布在CodePlex中的MvcPatch項目中,如果您感興趣可以獲取它的源代碼并編譯使用。
目前,MvcPatch包含兩個修改,一個自然就是目前WebViewEngine的問題,而另一個便是之前提過的DefaultControllerFactory線程安全問題,以后我會補充更多設(shè)計方面的修改和擴(kuò)展。在使用MvcPatch的時候,除了讓您的項目引用正確的程序集之外,還必須將web.config文件中各類型的名稱指向修改正確。因為使用ASP.NET MVC的模板創(chuàng)建項目時,它的web.config會使用GAC中注冊的強類型的ASP.NET MVC 1.0程序集。如果修改不正確,在使用MvcPatch的程序集時便會遇到錯誤。
因此我們也可以發(fā)現(xiàn),使用MvcPatch的好處在于,我們不需要使用外部擴(kuò)展的方式來構(gòu)建workaround,但是它也有缺點,那就是一些依賴于ASP.NET MVC 1.0程序集的項目無法和我們一起使用了。好在目前看起來這些項目都是些開源產(chǎn)品,如Telerik Extensions for ASP.NET MVC,我們可以下載它們的源代碼,基于MvcPatch的程序集編譯后再使用。
您別嫌麻煩,這就是享受開源的優(yōu)勢時需要付出的小小代價。
***再談一件事情。昨天晚上寫完文章之后,我想到這種“補丁版本”并不是長久之計,因此在CodePlex上給ASP.NET項目提了一個Issue:WebFormView總是輸出至HttpContext.Current而不是指定的TextWriter。今天早上發(fā)現(xiàn)已經(jīng)有了ASP.NET團(tuán)隊成員回復(fù),他們表示內(nèi)部的代碼庫中已經(jīng)修改了這個問題,將會體現(xiàn)在ASP.NET MVC 2的Preview 2版本中。
原文標(biāo)題:WebFormView的標(biāo)準(zhǔn)修改辦法及MvcPatch項目
鏈接:http://www.cnblogs.com/JeffreyZhao/archive/2009/09/15/standard-webformview-patch-and-MvcPatch-project.html
【編輯推薦】