代码改变世界

应该算是WebFormView的一个Bug

  Jeffrey Zhao  阅读(12554)  评论(24编辑  收藏  举报

最近需要搞一些重要的功能,结果又遇到了意料外的障碍。于是又仔细地看了看ASP.NET和ASP.NET MVC的源代码,又发现了以前不曾知道的一些细节。您最多说ASP.NET WebForms模型不一定适合某些Web应用程序的开发,但是我想没有人可以否认ASP.NET中设计的巧妙——以及复杂程度。其实ASP.NET为我们留下了不少切入点,但几乎没什么书会提到这些切入点,我们只能从微软自己的框架中一探究竟。

不过这次我想谈的是ASP.NET MVC框架中的一个Bug,这个Bug在一般情况下不会出现问题,但是这的确违反了ASP.NET MVC自身的设计。这个问题就出在WebFormView对象的实现上。

WebFormView是一个视图对象的实现。而在ASP.NET MVC中,任何视图都需要实现一个IView接口:

public interface IView {
    void Render(ViewContext viewContext, TextWriter writer);
}

Render方法的目的自然是根据ViewContext对象中的数据,将视图内容输出至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);
    }
}

虽然我认为这里的做法是不太妥当的(这点下次再谈),但是这的的确确地表现了Render方法的设计意图。只可惜在WebFormView中,Render方法却违背了这一设计:

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实例,并调用其RenderView方法生成内容……但是,我们的writer参数到哪里去了?没错,对writer参数Find All Reference就会发现,这个参数根本没有用到。既然在这里就已经抛弃了我们指定writer,那么接下来的逻辑再怎么搞也就“那么一回事儿”了。

如果您感兴趣阅读代码的话,会发现事实上最终这个对象被放入了一个新建的ViewPage对象中,然后调用ViewPage的RenderView方法生成视图内容:

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方法何时调用,永远是向HttpContext.Current输出内容。这个设计很不合理,但是修改起来还是非常简单的,例如以下几行代码就可以得到差不多的效果:

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的确可以扩展,但如果需要按照标准扩展的话,我们作的事情就多了:

  1. 继承WebFormView,覆盖RenderView方法。
  2. 继承WebFormViewEngine,覆盖CreatePartialView方法,返回刚创建的新类。
  3. 在Application Start时,使用新的ViewEngine类替换ASP.NET MVC原有的视图引擎。

但是在实际情况中,我会选择使用使用第三种方法:下载ASP.NET MVC的源代码,改写,编译。既然它是MS-PL的授权协议,为什么不自己动手打一些Patch呢?事实上,我也打算使用这种方法来修补ASP.NET MVC的Bug或Design Issue,并发布一个临时的新项目,就叫作……MvcPatch如何?

编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
历史上的今天:
2006-09-14 修补AJAX应用中Back/Forward Button和Bookmark失效的问题
点击右上角即可分享
微信分享提示