仅此一文让你明白ASP.NET MVC 之View的显示

摘要:
MVC中的Controller如何找到View,并进行显示。这个东西大家用MVC的时候常常接触,比如:publicclassHomeController:Controller{publicActionResultIndex(){returnView();}publicActionResultGetInfo(){...returnJson;}publicActionResultGetContent(){returnContent;}}上面三个Action返回分别对应ViewResult、JsonResult、ContentResult,而我图中只画ViewResult,因为它是我们最常用的一个ActionResult,而且是最复杂的一个。为了方便大家理解,我们看看RazorViewEngine的源码:publicclassRazorViewEngine:BuildManagerViewEngine{internalstaticreadonlystringViewStartFileName="_ViewStart";//存储ViewStart模板的publicRazorViewEngine:base{//这些构造大家应该觉得很亲切ViewLocationFormats=new[]{"~/Views/{1}/{0}.cshtml","~/Views/{1}/{0}.vbhtml","~/Views/Shared/{0}.cshtml","~/Views/Shared/{0}.vbhtml"};MasterLocationFormats=new[]{"~/Views/{1}/{0}.cshtml","~/Views/{1}/{0}.vbhtml","~/Views/Shared/{0}.cshtml","~/Views/Shared/{0}.vbhtml"};PartialViewLocationFormats=new[]{"~/Views/{1}/{0}.cshtml","~/Views/{1}/{0}.vbhtml","~/Views/Shared/{0}.cshtml","~/Views/Shared/{0}.vbhtml"};FileExtensions=new[]{"cshtml","vbhtml",};}protectedoverrideIViewCreateView{varview=newRazorView{DisplayModeProvider=DisplayModeProvider};returnview;}}从代码中可以看出,RazorViewEngine只是封装了View文件的相关路径。

有些人要问题,为什么我要学框架?这里我简单说一下,深入理解一个框架,给你带来最直接的好处:

  1. 使用框架时,遇到问题可以快速定位,并知道如何解决;
  2. 当框架中有些功能用着不爽时,你可以自由扩展,实现你想要的操作,甚至可以拿到源码直接修改;
  3. 想成为框架师的必经之路;
  4. 提取框架中的优秀代码和思想,为己所用;

更多好处,你可以自己去体会,有兴趣的可以看一下asp.net中 mvc部分的源码:http://aspnetwebstack.codeplex.com/

本文目的

上一篇文章是让你明白MVC最核心的两个流程(如果没看过请猛戳这里,只需耽误你打盹的几分钟)。我们接着上篇从ControllerActionInvoker的InvokeAction方法执行Action说起:

复制代码
//执行Action,并得到ActionResult
    ActionResult actionResult = method.Invoke(controllerContext.Controller,
        parameters.ToArray()) as ActionResult;

    //最终ActionResult用HttpResponse将数据传回客户进行显示
    actionResult.ExecuteResult(controllerContext);
复制代码

本文的目的就是让你明白这段代码到底做了哪些事情?MVC中的Controller如何找到View,并进行显示。

开始旅程

先放上与本文相关的类结构图:

http://www.cnblogs.com/DotCpp/

此图看起来结构相当复杂,但其实他很简单,给我两分钟,我会让你明白这些鬼东西到底是什么、有什么关系?

先说ActionResult,从图上看,ActionResult是个抽象类,他的子类有很多,比如JsonResult,ContentResult,ViewResult,EmptyResult等等。这个东西大家用MVC的时候常常接触,比如:

复制代码
public class HomeController:Controller
{
    public ActionResult Index()
    {
         return View();
    }

    public ActionResult GetInfo()
    {
         ...
        return Json(obj);
     }
 
     public ActionResult GetContent()
     {
         return Content("test");
      }
}
复制代码

上面三个Action返回分别对应ViewResult、JsonResult、ContentResult,而我图中只画ViewResult,因为它是我们最常用的一个ActionResult,而且是最复杂的一个(因为要负责View的显示)。而看看ContentResult的核心源码,我想简单的大家都笑翻了:

复制代码
public class ContentResult : ActionResult
    {
        public string Content { get; set; }

        public override void ExecuteResult(ControllerContext context)
        {
            HttpResponseBase response = context.HttpContext.Response;
            
            if (Content != null)
            {
                response.Write(Content);
            }
        }
    }
复制代码

它的实现和上面我画的结构图完全没有半毛钱关系,直接一个Response.Write输出就完成了。所以我用ViewResult来写本文。

接下来注意ViewEngines这个静态类,看一下它的源码:

复制代码
 public static class ViewEngines
    {
        private static readonly ViewEngineCollection _engines = new ViewEngineCollection
        {
            new WebFormViewEngine(),
            new RazorViewEngine(),
        };

        public static ViewEngineCollection Engines
        {
            get { return _engines; }
        }
    }
复制代码

只有一个静态只读的ViewEngineCollection类型的成员,初始化时封装了两个视图引擎,WebFormViewEngine和RazorViewEngine?这两个是什么?为了方便大家理解,我们看看RazorViewEngine的源码(WebFormViewEngine基本一样,篇幅限制下面我只用Razor举例):

复制代码
public class RazorViewEngine : BuildManagerViewEngine
    {
        internal static readonly string ViewStartFileName = "_ViewStart"; //存储ViewStart模板的

        public RazorViewEngine(IViewPageActivator viewPageActivator)
            : base(viewPageActivator)
        {
           //这些构造大家应该觉得很亲切
            ViewLocationFormats = new[]
            {
                "~/Views/{1}/{0}.cshtml",
                "~/Views/{1}/{0}.vbhtml",
                "~/Views/Shared/{0}.cshtml",
                "~/Views/Shared/{0}.vbhtml"
            };
            MasterLocationFormats = new[]
            {
                "~/Views/{1}/{0}.cshtml",
                "~/Views/{1}/{0}.vbhtml",
                "~/Views/Shared/{0}.cshtml",
                "~/Views/Shared/{0}.vbhtml"
            };
            PartialViewLocationFormats = new[]
            {
                "~/Views/{1}/{0}.cshtml",
                "~/Views/{1}/{0}.vbhtml",
                "~/Views/Shared/{0}.cshtml",
                "~/Views/Shared/{0}.vbhtml"
            };

            FileExtensions = new[]
            {
                "cshtml",
                "vbhtml",
            };
        }


        protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
        {
            var view = new RazorView(controllerContext, viewPath,
                                     layoutPath: masterPath, runViewStartPages: true, viewStartFileExtensions: FileExtensions, viewPageActivator: ViewPageActivator)
            {
                DisplayModeProvider = DisplayModeProvider
            };
            return view;
        }
    }
复制代码

从代码中可以看出,RazorViewEngine只是封装了View文件的相关路径。后面我会说明他们是怎么被用到的。

知道上面几个类的基本情况后,看一下它们的执行流程,你会对各个类的功能有个大致的了解,当控制器的Action返回一个ViewResult时并执行ExecuteResult时(文章开始部分介绍的代码):

  1. 从ViewEngines的Engines中获取ViewResultEngine对象,实质是遍历RazorViewEngine和WebFormViewEngine,然后通过它们本身继承VirtualPathProviderViewEngine的成员函数FindView创建ViewResultEngine【从下面的时序图中可以看出,虽然MVC把这个东西藏的很深,但基本都是在父类与子类间传递,结构还算清晰】;
  2. 通过得到的ViewResultEngine中的View执行Render进行界面显示,内部会调用RazorView的RenderView进行最终的显示处理;

核心流程就是上面两步,执行时序图如下所示(点击查看大图):

http://www.cnblogs.com/DotCpp/

现在注意流程的第17步,即执行BuildManagerCompliedView的Render函数,这个函数是View显示的灵魂:

复制代码
        public virtual void Render(ViewContext viewContext, TextWriter writer)
        {
            object instance = null;
            
            //这个ViewPath就是根据RazorViewEngine的模板位置得到的View具体路径,在RazorViewEngine创建ViewEngineResult传进来的
            Type type = BuildManager.GetCompiledType(ViewPath);
            if (type != null)
            {
                instance = ViewPageActivator.Create(_controllerContext, type);
            }

            if (instance == null)
            {
                throw new InvalidOperationException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        MvcResources.CshtmlView_ViewCouldNotBeCreated,
                        ViewPath));
            }

            RenderView(viewContext, writer, instance);
        }
复制代码

上面根据ViewPath生成的那个instance是什么?看看RazorView中RendView的实现就知道了:

复制代码
 protected override void RenderView(ViewContext viewContext, TextWriter writer, object instance)
        {
            WebViewPage webViewPage = instance as WebViewPage;
            //其它代码先不管
            webViewPage.ExecutePageHierarchy(new WebPageContext(context: viewContext.HttpContext, page: null, model: null), writer, startPage);
        }
复制代码

原来是个WebViewPage的对象,查一查MSDN就知道,所有View都是从WebViewPage的泛型WebViewPage<TMode>直接继承出来的。我们来看一下这个类:

复制代码
    public abstract class WebViewPage : WebPageBase, IViewDataContainer, IViewStartPageChild
    {
        private ViewDataDictionary _viewData;
        private DynamicViewDataDictionary _dynamicViewData;

        public AjaxHelper<object> Ajax { get; set; }
        public HtmlHelper<object> Html { get; set; }

        public object Model
        {
            get { return ViewData.Model; }
        }

        public TempDataDictionary TempData
        {
            get { return ViewContext.TempData; }
        }

        public UrlHelper Url { get; set; }

        public dynamic ViewBag
        {
            get
            {
                if (_dynamicViewData == null)
                {
                    _dynamicViewData = new DynamicViewDataDictionary(() => ViewData);
                }
                return _dynamicViewData;
            }
        }

        public ViewContext ViewContext { get; set; }

        public override void ExecutePageHierarchy()
        {
              Execute();  //这个函数是基类中定义的抽象函数,会在最终aspx/cshtml生成的类中被重载
        }

        public virtual void InitHelpers()
        {
            Ajax = new AjaxHelper<object>(ViewContext, this);
            Html = new HtmlHelper<object>(ViewContext, this);
            Url = new UrlHelper(ViewContext.RequestContext);
        }
    }
复制代码

是不是在里面看到了很多平时最常用的属性。当用ExecutePageHierarchy生成页面时,实际会调用Execute函数,这个函数会在最终cshtml,aspx等生成的类中被重载。我们看一个简单的例子:

假设我们有一个强类型的视图:/Views/Home/Index.cshtml,一个简单的DemoModel类型,只有一个UserName属性:

@model Controllers.DemoModel
<div>Index</div>
@Model.UserName

那么这个Index.cshtml被编译后就会生成下面这个类:

复制代码
public class _Page_Views_Home_Index_cshtml : WebViewPage<DemoModel>
{
    public override void Execute()
    {
          this.WriteLiteral("<div>Index</div");
          this.Write(Model.UserName);
    }
}
复制代码

就这样,最终将WEB显示出来。

免责声明:文章转载自《仅此一文让你明白ASP.NET MVC 之View的显示》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇超详细简单小白安装GIT教程awk入门及进阶下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

随便看看

T420s成功加装固态硬盘(SSD)

目的为了提高系统和常用工具的启动速度,在ThinkPadT420s光驱中添加了一个固态磁盘。将SSD安装到硬盘机架:将硬盘机架附带的四个塑料螺钉拧入SSD侧面,以将SSD固定在硬盘机架中。京东推荐的坏东西与T420不匹配。启动后无法识别SSD。将T420s更换为第一个可以正确识别的硬盘。结论ThinkPadT420s的光驱支持SSD安装,而原始的机械硬盘安装...

sikuli简介

Sikuli脚本自动化,你在屏幕上看到的任何东西。Sikuli是一个开放源码的最初的用户界面设计组织在麻省理工学院的研究项目。现在是保持并进一步协调与开源社区开发的Sikuli实验室在美国科罗拉多州博尔德大学。Sikuli的MIT许可证下发布的。当然,你也可以使用sikuli的javaAPI使其在java环境下运行。小例子大体上了解sikuli的界面,下面来...

配置nginx

aNULL:!MD5:!...

C#探秘系列(十)WPF:打开文件选择器选择文件并保存

//此为点击按钮的监听事件,点击按钮弹出文件选择器privatevoidimageButton_Click(objectsender,RoutedEventArgse){vardialog=newOpenFileDialog();dialog.Filter=".jpg|*.jpg|.png|*.png|.jpeg|*.jpeg";if(dialog.Show...

HTTP请求报文

不仅报表样式可以传递请求参数,请求url也可以以类似于键值对的方式传递数据...

微信支付服务商模式支付与普通微信支付的配置区别

chapter=7_7&index=5注:与普通微信支付相比,源代码是上述7/8之间的区别,其他可以看作是服务提供商自己的微信支付配置;...