MVC的View本质和扩展

一:网站启动流程简介

前面两节我们有介绍管道处理模型,然后下图总结出了mvc启动的整个流程

二:MVC返回的三种结果

从之前的流程已经反编译源码我们晓的,mvc最终都会返回一个结果,其中大概分为以下三种:

1:返回ActionResult:是一个抽象类,实现了ExecuteResult,源码如下:

2:EmptyResult:返回void的,然后该类继承于ActionResult

3:其他类型:比如JsonResult,String,DateTime,XML,File等,其实最终结果都是ToString()然后写入response,下图我们以JsonResult的源码中的ExecuteResult()方法中的片段代码来说明

三:MVC中ViewResult视图

在新建一个mvc项目中,我新建一个视图,当访问时,一般大家都会默认约定俗成的去找:Views-》控制器的名字 -》视图名字,然后浏览器访问的时候会直接访问:控制器名字/视图名字,这样即可找到了对应的视图。

实例如下:

那现在我们看一下View()这是什么?为什么直接这样写就能访问到对应的cshtml页面呢?在不看源码之前我们可以做如下猜想,第一步一定会先找view,第二步会输出对应的内容到页面上面,那我们的猜想究竟是否正确呢,我们以源码来一步一步的解释说明。

点击View类一步一步的查看,发现View--》ViewResult--》--》ViewResultBase--》ActionResult--》ExecuteResult。

1:找视图:ViewEngineCollection来寻找的,但是通过源码找到的是IView,之前我们不是寻找的cshtml的吗?那IView是怎么转换成cshtml的呢?带着介个疑问,我们来打开我们的cshtml,之前我们都有了解过,cshtml中是可以写html代码也是可以后台代码,那这两者是怎么结合在一起的呢?原因如下:

A:cshtml为啥能写后台代码

cshtml的基类是System.Web.Mvc.WebViewPage,具体体现在:views文件夹下面的web.config,如下:

所以这就解释了为啥我们cshtml里面没有引用任何的命名空间,二却可以写@Html,@ViewBag,@model,@base等后台代码的变量,原来这些通用的命名空间全部是在views下面的web.config中配置的。webViewPage类中都有Html,ViewBag,ViewData等这些变量。

了解了这些后,我们可以对介个基类WebViewPage进行扩展,

比如我们可以新建一个类然后继承于WebViewPage这个类,然后新类里面可以增加我们自己特有的属性,比如登录用户的一些信息等,这样到时候把webconfig中的pageBaseType修改我们新建类,以后所有的cshtml都可以直接使用新增的特性了。

比如我们可以把所有的cshtml通用的命名空间统一放在namespaces介个命名空间,然后不需要每个cshtml都重复引用了。

B:cshtml类中的前台代码跟后台代码怎么融合在一起的。很多人会想到模板方法然后字符串替换,但是cshtml类不是这样实现的。cshtml是把整个cshtml方法当成一个字符串,然后以后台代码为主,就是遇到html然后拼接成字符串,然后遇到后台代码则正常执行。

下面我们通过一个方法来进行分析说明:

 1  /// <summary>
 2  /// 一个展示当前网站的view文件的
 3  /// </summary>
 4  /// <param name="helper"></param>
 5  /// <returns></returns>
 6  public static MvcHtmlString ListViewAssemblies(this HtmlHelper helper)
 7  {
 8      TagBuilder ul = new TagBuilder("ul");
 9      foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies().Where(a => a.FullName.StartsWith("App_Web_")))//view编辑后生成的都是以App_Web_开头的
10      {
11          TagBuilder li = new TagBuilder("li");
12          li.InnerHtml = string.Format("dll完整名称:{0}", assembly.FullName);
13          ul.InnerHtml += li.ToString();
14 
15          TagBuilder li2 = new TagBuilder("li");
16          li2.InnerHtml = string.Format("dll地址:{0}", assembly.Location);
17          ul.InnerHtml += li2.ToString();
18      }
19      return MvcHtmlString.Create(ul.ToString());
20  }

然后新建一个view如下:

@using Ruanmou.Web.Core.Extension;
@{
    ViewBag.Title = "ViewShow";
}

<h2>ViewShowUpdate</h2>
<h2>ViewShow2</h2>
<div>当前View类型:@this.GetType().AssemblyQualifiedName</div>
<div>@{base.Response.Write("这里是直接write");}</div>
<div>BuildManager:@ViewBag.ViewClass</div>
<div>当前加载的View程序集1111111:</div>
@Html.ListViewAssemblies()

预览发现:

这样我们找到dll地址:C:WindowsMicrosoft.NETFramework64v4.0.30319Temporary ASP.NET Files ootc746e81fcd88313e,然后打开介个地址,使用反编译看一下App_Web_dzfxeipk.dll,会发现:

即每个controller会对应一个dll类,然后里面所有的view都会生成一个类。然后该cshtml用到的views全部会统一生成dll。

C:为啥cshtml最终解析的是后台代码,但是我们修改了cshtml代码后,不需要编译,而直接能访问修改呢。这是因为cshtml是即时编译的,它会有一个文件监控,当你访问的时候,如果视图发生改变,则及时编译成新的dll,如果没有改变,则直接以原先的编译的dll为主。

 编译是通过下面一个类来实现的:

Type type = System.Web.Compilation.BuildManager.GetCompiledType("~/Views/Pipe/ViewShow.cshtml");
ViewBag.ViewClass = type.FullName;
//它调用BuildManager的静态方法GetCompiledType根据指定的View文件虚拟路径得到编译后的WebPageView类型,
//然后将该类型交给ViewPageActivator激活一个具体的WebPageView对象,并调用其Render方法完成对View的最终呈现

编译的过程总结如下:

1:ASP.NET MVC对View文件进行动态编译生成的类型名称基于View文件的虚拟路径,(比如文件路径为“~/Views/Pipe/Action1.cshtml”的View对应的类型为“ASP._Page_Views_Pipe_Action1_cshtml”)。
2:ASP.NET MVC是按照目录进行编译的(“~/Views/Pipe/”下的View文件最终都被编译到一个程序集“App_Web_j04xtjsy”中)。
3:程序集按需加载,即第一次访问“~/View/Pipe/”目录下的View并不会加载针对“~/View/Home/”目录的程序集(实际上此时该程序集尚未生成)。

2:绘画成Html代码

最终cshtml会变成一个后台类,然后在execute中把html代码转换为类,最终Resonse来输出到页面上面。

 

然后反编译webViewPage找到方法write,即是组装output,然后统一输出。

四:根据mvc反编译,来扩展View视图

扩展的目标:不同的浏览器访问不同的view视图。具体做法如下:

1:新增类CustomViewEngine继承于RazorViewEngine(因为现在是mvc项目,所以选择继承于RazorViewEngine),代码如下:

 1 public class CustomViewEngine : RazorViewEngine
 2 {
 3     #region 构造函数
 4         public CustomViewEngine() : this(null)
 5         {
 6         }
 7         public CustomViewEngine(IViewPageActivator viewPageActivator) : base(viewPageActivator)
 8         {
 9             this.SetEngine();
10         }
11         #endregion
12 
13     public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
14         {
15             if (controllerContext.HttpContext.Request.UserAgent.Contains("Chrome/74.0.3729.169"))
16             {
17                 this.SetEngine("Chrome");
18             }
19             else
20             {
21                 this.SetEngine();//一定得有,因为只有一个Engine实例
22             }
23             return base.FindView(controllerContext, viewName, masterName, useCache);
24         }
25 
26     public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
27         {
28             if (controllerContext.HttpContext.Request.UserAgent.Contains("Chrome/74.0.3729.169"))
29             {
30                 this.SetEngine("Chrome");
31             }
32             else
33             {
34                 this.SetEngine();
35             }
36             return base.FindPartialView(controllerContext, partialViewName, useCache);
37         }
38     /// <summary>
39     /// 把模板给换了
40     /// </summary>
41     /// <param name="browser"></param>
42     private void SetEngine(string browser="")
43         {
44             base.AreaViewLocationFormats = new string[]
45                         {
46                 "~/Areas/{2}/"+browser+"Views/{1}/{0}.cshtml",
47                 "~/Areas/{2}/"+browser+"Views/{1}/{0}.vbhtml",
48                 "~/Areas/{2}/"+browser+"Views/Shared/{0}.cshtml",
49                 "~/Areas/{2}/"+browser+"Views/Shared/{0}.vbhtml"
50                         };
51             base.AreaMasterLocationFormats = new string[]
52             {
53                 "~/Areas/{2}/"+browser+"Views/{1}/{0}.cshtml",
54                 "~/Areas/{2}/"+browser+"Views/{1}/{0}.vbhtml",
55                 "~/Areas/{2}/"+browser+"Views/Shared/{0}.cshtml",
56                 "~/Areas/{2}/"+browser+"Views/Shared/{0}.vbhtml"
57             };
58             base.AreaPartialViewLocationFormats = new string[]
59             {
60                 "~/Areas/{2}/"+browser+"Views/{1}/{0}.cshtml",
61                 "~/Areas/{2}/"+browser+"Views/{1}/{0}.vbhtml",
62                 "~/Areas/{2}/"+browser+"Views/Shared/{0}.cshtml",
63                 "~/Areas/{2}/"+browser+"Views/Shared/{0}.vbhtml"
64             };
65             base.ViewLocationFormats = new string[]
66             {
67                 "~/"+browser+"Views/{1}/{0}.cshtml",
68                 "~/"+browser+"Views/{1}/{0}.vbhtml",
69                 "~/"+browser+"Views/Shared/{0}.cshtml",
70                 "~/"+browser+"Views/Shared/{0}.vbhtml"
71             };
72             base.MasterLocationFormats = new string[]
73             {
74                 "~/"+browser+"Views/{1}/{0}.cshtml",
75                 "~/"+browser+"Views/{1}/{0}.vbhtml",
76                 "~/"+browser+"Views/Shared/{0}.cshtml",
77                 "~/"+browser+"Views/Shared/{0}.vbhtml"
78             };
79             base.PartialViewLocationFormats = new string[]
80             {
81                 "~/"+browser+"Views/{1}/{0}.cshtml",
82                 "~/"+browser+"Views/{1}/{0}.vbhtml",
83                 "~/"+browser+"Views/Shared/{0}.cshtml",
84                 "~/"+browser+"Views/Shared/{0}.vbhtml"
85             };
86         }
87 }
View Code

2:新增views视图,可以把之前的views视图全部copy一份出来命名为:ChromeViews

3:在Global.asax类中Application_Start()方法新增配置,即把之前的视图引擎修改为自定义的CustomViewEngine。

1 ViewEngines.Engines.Clear();
2 ViewEngines.Engines.Add(new CustomViewEngine());

这样即实现了不同的chrom浏览器访问就会跳转到ChromeViews视图里面,其它的浏览器则默认走Views里面。可以仿照上面的代码来进行pc/APP端视图切换,多语言视图切换,即controller使用的是一套,但是View使用多个。

原文地址:https://www.cnblogs.com/loverwangshan/p/11215050.html