• 仅此一文让你明白ASP.NET MVC 之View的显示(仅此一文系列二)


    题外话

    一周之前写的《仅此一文让你明白ASP.NET MVC原理》受到了广大学习ASP.NET MVC同学的欢迎,于是下定决心准备把它写成一个系列,以满足更多求知若渴的同学们。蒋金楠老师已经在他的《ASP.NET MVC 4框架揭秘》书中已经做了很深入的讲解。我总不能把他的文章抄下来放给大家。那大家还不如看他的博客去。我想做的就是给大家提供基于图形化、直观、系统、简洁的理解。部分内容想深入理解的同学,还是花点银子去买本他的书,非常值得一看(绝非打广告⊙﹏⊙‖∣)。

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

    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显示出来。

    后记

    本以为这篇文章很好写,没想到断断续续地写了两天。还是那句话:希望新手看的明白,老手多提意见,谢谢!

    接下来一篇会放出View显示的重要组成部分:Model Data的讲解(难道你们看着看着没觉得少了点什么吗?讲了半天流程,实际的数据如何传递?如何显示?),敬请关注!

  • 相关阅读:
    linux查看端口
    linux下Git代码
    linux安装mysql
    pip工具更新及解决"No module named pip"问题
    demo-bootstrap实现滑动开关
    vue 随笔
    css 解决盒子移动鼠标丢失产生的抖动问题
    笔记-纯css实现select的placeholder
    笔记-移动端rem适配和解决安卓手机键盘唤起引起样式问题
    demo-tab切换
  • 原文地址:https://www.cnblogs.com/yubaolee/p/3293056.html
Copyright © 2020-2023  润新知