代码改变世界

ASP.NET MVC Preview 2 - 流程分析 (3)

2008-04-30 13:18  Jacky_Xu  阅读(625)  评论(0编辑  收藏  举报
作为 MVC 的最后一步,就是要将精心准备的数据展示出来。Controller 提供了几个重载的 RenderView() 来完成这个工作。
public class Controller : IController
{
  protected virtual void RenderView(string viewName, string masterName, object viewData)
  {
    ViewContext viewContext = new ViewContext(this.ControllerContext, viewName, masterName, viewData, this.TempData);
    this.ViewEngine.RenderView(viewContext);
  }

  public IViewEngine ViewEngine
  {
    get
    {
      return (this._viewEngine ?? new WebFormViewEngine());
    }
    set
    {
      // ...
      this._viewEngine = value;
    }
  }
}

方法很简单,将一路传递过来的相关 "环境数据" (上下文) 再次包装。当然,这次依然会多出些东西,里面就有我们向视图传递的数据 —— viewData & tempData。作为默认选择,MVC 创建 WebForm 视图引擎来展示结果 (我个人更倾向使用 NVelocity Template Engine,会在后面的章节提供源代码)。继续跳转,我们看看这个 WebFormViewEngine 是何方高人。
public class WebFormViewEngine : IViewEngine
{
  protected virtual void RenderView(ViewContext viewContext)
  {
    // ...

    string viewLocation = this.ViewLocator.GetViewLocation(viewContext, viewContext.ViewName);

    // ...

    object obj2 = this.BuildManager.CreateInstanceFromVirtualPath(viewLocation, typeof(object));

    // ...

    ViewPage page = obj2 as ViewPage;
    if (page != null)
    {
      if (!string.IsNullOrEmpty(viewContext.MasterName))
      {
        string masterLocation = this.ViewLocator.GetMasterLocation(viewContext, viewContext.MasterName);
        
        // ...
        
        page.MasterLocation = masterLocation;
      }

      page.SetViewData(viewContext.ViewData);
      page.RenderView(viewContext);
    }
    else
    {
      ViewUserControl control = obj2 as ViewUserControl;

      // ...

      if (!string.IsNullOrEmpty(viewContext.MasterName))
      {
        throw new InvalidOperationException(MvcResources.WebFormViewEngine_UserControlCannotHaveMaster);
      }

      control.SetViewData(viewContext.ViewData);
      control.RenderView(viewContext);
    }
  }

  public IViewLocator ViewLocator
  {
    get
    {
      if (this._viewLocator == null)
      {
        this._viewLocator = new WebFormViewLocator();
      }

      return this._viewLocator;
    }
    set
    {
      this._viewLocator = value;
    }
  }
}

精简了一下代码,看上去不算很复杂。首先会创建一个 WebFormViewLocator 对象来获取视图存放路径,下面的代码对于已经使用 MVC 的兄弟应该很熟悉了。
public class WebFormViewLocator : ViewLocator
{
  public WebFormViewLocator()
  {
    base.ViewLocationFormats = new string[]
    {
      "~/Views/{1}/{0}.aspx",
      "~/Views/{1}/{0}.ascx",
      "~/Views/Shared/{0}.aspx",
      "~/Views/Shared/{0}.ascx"
    };
    
    base.MasterLocationFormats = new string[]
    {
      "~/Views/{1}/{0}.master",
      "~/Views/Shared/{0}.master"
    };
  }

  protected virtual string GetViewLocation(RequestContext requestContext, string viewName)
  {
    // ...

    string str = this.GetPath(requestContext, this.ViewLocationFormats, viewName);

    // ...

    return str;
  }
}

在获取路径后,WebFormViewEngine 通过一个包装类 BuildManagerWrapper 间接调用 System.Web.Compilation.BuildManager 的静态方法 CreateInstanceFromVirtualPath() 将视图进行编译,并返回一个对象实例。(System.Web.Compilation.BuildManager 提供一组有助于管理 ASP.NET 应用程序编译的方法)

再往后的方法就很有趣了,通过 as 转换结果来判断视图是 ViewPage 还是 ViewUserControl。呵呵~~~ 我承认,我也经常写类似的代码。ViewPage 继承自我们所熟悉的 System.Web.UI.Page,它的 RenderView() 方法也不过是完成 Page.ProcessRequest() 的最终处理而已。
public class ViewPage : Page, IViewDataContainer
{
  public virtual void RenderView(ViewContext viewContext)
  {
    this.ViewContext = viewContext;
    this.Ajax = new AjaxHelper(viewContext);
    this.Html = new HtmlHelper(viewContext);
    this.Url = new UrlHelper(viewContext);

    this.ProcessRequest(HttpContext.Current);
  }
}

反而是 ViewUserControl 比较好玩,你会注意到,我们可以直接在 Controller 中 RenderView 一个用户控件(ViewUserControl),这似乎有些古怪。如果你继续深入跟踪 "control.RenderView(viewContext);" 就会看到如下代码,某些 "好人" 会替我们创建了一个 "空白页" 来装载这个控件。当然,RenderView(ViewUserControl) 有个限制,就是不能有 Master。
public class ViewUserControl : UserControl, IViewDataContainer
{
  public virtual void RenderView(ViewContext viewContext)
  {
    viewContext.HttpContext.Response.Cache.SetExpires(DateTime.Now);
    new ViewUserControlContainerPage(this).RenderView(viewContext);
  }
}

private sealed class ViewUserControlContainerPage : ViewPage
{
  public ViewUserControlContainerPage(ViewUserControl userControl);
}

好了,RenderView() 到此为止。

我们从 UrlRoutingModule 开始,历经 MvcRouteHandler、MvcHandler、Controller、ActionFilterAttribute,直到最后的 ViewEngine、ViewPage,总算是完成了一次 "探索之旅"。虽然过程有些简单,但对于我们理解和更好地使用 MVC 却多少有些帮助。后面章节相关功能开发和扩展,都会用到这些分析结果,大家还是不要偷懒的好…… [lol]

---------------------

附: 流程分析图

uploads/200803/12_223841_mvc3.png


查看大图