一:网站启动流程简介
前面两节我们有介绍管道处理模型,然后下图总结出了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 }
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使用多个。