淺析WebFormView中的一個Bug
老趙的文章主要是針對WebFormView中的一個Bug,這個Bug在一般情況下不會出現(xiàn)問題,它沒有遵循ASP.NET MVC既定的模型。
最近需要搞一些重要的功能,結(jié)果又遇到了意料外的障礙。于是又仔細地看了看ASP.NET和ASP.NET MVC的源代碼,又發(fā)現(xiàn)了以前不曾知道的一些細節(jié)。您最多說ASP.NET WebForms模型不一定適合某些Web應用程序的開發(fā),但是我想沒有人可以否認ASP.NET中設(shè)計的巧妙——以及復雜程度。其實ASP.NET為我們留下了不少切入點,但幾乎沒什么書會提到這些切入點,我們只能從微軟自己的框架中一探究竟。
不過這次我想談的是ASP.NET MVC框架中的一個Bug,這個Bug在一般情況下不會出現(xiàn)問題,但是這的確違反了ASP.NET MVC自身的設(shè)計。這個問題就出在WebFormView對象的實現(xiàn)上。
WebFormView是一個視圖對象的實現(xiàn)。而在ASP.NET MVC中,任何視圖都需要實現(xiàn)一個IView接口:
- public interface IView {
- void Render(ViewContext viewContext, TextWriter writer);
- }
Render方法的目的自然是根據(jù)ViewContext對象中的數(shù)據(jù),將視圖內(nèi)容輸出至TextWriter中。例如在HtmlHelper的RenderPartial方法,便是將一個Partial View輸出至Response中.
- public class HtmlHelper {
- ...
- internal virtual void RenderPartialInternal(
- string partialViewName,
- ViewDataDictionary viewData,
- object model,
- ViewEngineCollection viewEngineCollection) {
- ...
- ViewContext newViewContext = new ViewContext(...);
- IView view = FindPartialView(newViewContext, partialViewName, viewEngineCollection);
- view.Render(newViewContext, ViewContext.HttpContext.Response.Output);
- }
- }
雖然我認為這里的做法是不太妥當?shù)模ㄟ@點下次再談),但是這的的確確地表現(xiàn)了Render方法的設(shè)計意圖。只可惜在WebFormView中,Render方法卻違背了這一設(shè)計:
- public class WebFormView : IView {
- ...
- public virtual void Render(ViewContext viewContext, TextWriter writer) {
- ...
- object viewInstance = ...;
- ...
- ViewUserControl viewUserControl = viewInstance as ViewUserControl;
- if (viewUserControl != null) {
- RenderViewUserControl(viewContext, viewUserControl);
- return;
- }
- ...
- }
- private void RenderViewUserControl(ViewContext context, ViewUserControl control) {
- ...
- control.ViewData = context.ViewData;
- control.RenderView(context);
- }
- }
對于Partial View,WebFormView會加載合適的ViewUserControl實例,并調(diào)用其RenderView方法生成內(nèi)容……但是,我們的writer參數(shù)到哪里去了?沒錯,對writer參數(shù)Find All Reference就會發(fā)現(xiàn),這個參數(shù)根本沒有用到。既然在這里就已經(jīng)拋棄了我們指定writer,那么接下來的邏輯再怎么搞也就“那么一回事兒”了。
如果您感興趣閱讀代碼的話,會發(fā)現(xiàn)事實上最終這個對象被放入了一個新建的ViewPage對象中,然后調(diào)用ViewPage的RenderView方法生成視圖內(nèi)容:
- 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);
- }
- }
瞧到這個HttpContext.Current了嗎?也就是說,無論RenderView方法何時調(diào)用,永遠是向HttpContext.Current輸出內(nèi)容。這個設(shè)計很不合理,但是修改起來還是非常簡單的,例如以下幾行代碼就可以得到差不多的效果:
- public static class HtmlExtensions
- {
- public static void 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);
- htmlHelper.ViewContext.HttpContext.Server.Execute(
- page,
- htmlHelper.ViewContext.HttpContext.Response.Output,
- false);
- }
- }
但是我不喜歡這種做法,因為它沒有遵循ASP.NET MVC既定的模型。ASP.NET MVC的確可以擴展,但如果需要按照標準擴展的話,我們作的事情就多了:
繼承WebFormView,覆蓋RenderView方法。
繼承WebFormViewEngine,覆蓋CreatePartialView方法,返回剛創(chuàng)建的新類。
在Application Start時,使用新的ViewEngine類替換ASP.NET MVC原有的視圖引擎。
但是在實際情況中,我會選擇使用使用第三種方法:下載ASP.NET MVC的源代碼,改寫,編譯。既然它是MS-PL的授權(quán)協(xié)議,為什么不自己動手打一些Patch呢?事實上,我也打算使用這種方法來修補ASP.NET MVC的Bug或Design Issue,并發(fā)布一個臨時的新項目,就叫作……MvcPatch如何?
原文標題:應該算是WebFormView的一個Bug
鏈接:http://www.cnblogs.com/JeffreyZhao/archive/2009/09/14/webviewengine-bug-always-render-to-current-context.html
【編輯推薦】