对于WebFormView的标准修改办法以及MvcPatch项目,这些东西的好处是如果我们在构建一个面向AJAX请求的Action,此时View的内容可能只是输出的一部分,甚至我们要对内容进行过滤/编码等额外操作。
在浅析WebFormView中的一个Bug中我提到WebFormView的实现破坏了IView对象设计思路,它会把视图内容直接生成至HttpContext.Current而不是Render方法指定的TextWriter中。目前,WebFormView.Render的调用方只有两个:ViewResult.ExecuteResult方法还有HtmlHelper.RenderPartial方法,但是这两者原本的目的地就是当前的HttpContext,因此在平时使用时WebFormView的错误实现并不会造成问题。
- 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();
- }
- }
但是,如果我们在构建一个面向AJAX请求的Action,此时View的内容可能只是输出的一部分,甚至我们要对内容进行过滤/编码等额外操作。此时,我们就希望指定一个TextWriter用于收集内容——但是WebFormView自然无法做到。之前我提出了一种非常临时,非常山寨,非常简陋,绕弯,但是可行,或者说是可以“表现出解决问题的方法”的代码,修改一下便能说明问题:
这个HtmlHelper的扩展方法Partial,和HtmlHelper自带的RenderPartial功能比较接近,不过Partial是将视图内容直接生成一个字符串并返回,RenderPartial方法是直接输出至当前HttpContext。因此它们在视图中的使用方式是不同的:
<% Html.RenderPartial("MyPartialView"); %>
<%= Html.Partial("MyPartialView") %>RenderPartial以<%开头,末尾有分号。而Partial以<%=开头,末尾没有分号。关于视图中的各种输出方式,我最近在阅读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内容
- var containerPage = new ViewUserControlContainerPage(this);
- // Tracing requires Page IDs to be unique.
- ID = Guid.NewGuid().ToString();
- // 其中会执行ViewUserControlContrainerPage的RenderView方法
- RenderViewAndRestoreContentType(containerPage, viewContext);
- }
- }
可见,在ViewPage和ViewUserControl中各有一个RenderView方法,它们只包含一个ViewContext参数,但是却没有输出目的地。因此,最终它们使用HttpContext.Current这个邪恶的、臭名昭著的静态属性来生成内容。现在想起来,我当时在搞异步Action时,遭遇异常而不得不手动保持HttpContext就是这个原因造成的。于是我们目前修改的方式,便是为ViewPage和ViewUserControl增加一个额外的TextWriter参数:
- 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的设计问题(Design Issue),但是这也是它的内部实现的低级错误。对于此类问题,如果使用扩展的方式进行修改会显得沉重而麻烦,需要各种扩展和配置才能使用。之前项目中使用的便是基于“外部扩展”来回避“内部错误”的办法,而目前已经换成自行修改编译过的System.Web.Mvc.dll了。这个修改版本目前已经发布在CodePlex中的MvcPatch项目中,如果您感兴趣可以获取它的源代码并编译使用。
目前,MvcPatch包含两个修改,一个自然就是目前WebViewEngine的问题,而另一个便是之前提过的DefaultControllerFactory线程安全问题,以后我会补充更多设计方面的修改和扩展。在使用MvcPatch的时候,除了让您的项目引用正确的程序集之外,还必须将web.config文件中各类型的名称指向修改正确。因为使用ASP.NET MVC的模板创建项目时,它的web.config会使用GAC中注册的强类型的ASP.NET MVC 1.0程序集。如果修改不正确,在使用MvcPatch的程序集时便会遇到错误。
因此我们也可以发现,使用MvcPatch的好处在于,我们不需要使用外部扩展的方式来构建workaround,但是它也有缺点,那就是一些依赖于ASP.NET MVC 1.0程序集的项目无法和我们一起使用了。好在目前看起来这些项目都是些开源产品,如Telerik Extensions for ASP.NET MVC,我们可以下载它们的源代码,基于MvcPatch的程序集编译后再使用。
您别嫌麻烦,这就是享受开源的优势时需要付出的小小代价。
***再谈一件事情。昨天晚上写完文章之后,我想到这种“补丁版本”并不是长久之计,因此在CodePlex上给ASP.NET项目提了一个Issue:WebFormView总是输出至HttpContext.Current而不是指定的TextWriter。今天早上发现已经有了ASP.NET团队成员回复,他们表示内部的代码库中已经修改了这个问题,将会体现在ASP.NET MVC 2的Preview 2版本中。
原文标题:WebFormView的标准修改办法及MvcPatch项目
链接:http://www.cnblogs.com/JeffreyZhao/archive/2009/09/15/standard-webformview-patch-and-MvcPatch-project.html
【编辑推荐】