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然后拼接成字符串,然后遇到后台代码则正常执行。

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

/// <summary>
 /// 一个展示当前网站的view文件的
 /// </summary>
 /// <param name="helper"></param>
 /// <returns></returns>
 public static MvcHtmlString ListViewAssemblies(this HtmlHelper helper)
 {
     TagBuilder ul = new TagBuilder("ul");
     foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies().Where(a => a.FullName.StartsWith("App_Web_")))//view编辑后生成的都是以App_Web_开头的
     {
         TagBuilder li = new TagBuilder("li");
         li.InnerHtml = string.Format("dll完整名称:{0}", assembly.FullName);
         ul.InnerHtml += li.ToString();

         TagBuilder li2 = new TagBuilder("li");
         li2.InnerHtml = string.Format("dll地址:{0}", assembly.Location);
         ul.InnerHtml += li2.ToString();
     }
     return MvcHtmlString.Create(ul.ToString());
 }

然后新建一个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:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\root\c746e81f\cd88313e\,然后打开介个地址,使用反编译看一下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),代码如下:

public class CustomViewEngine : RazorViewEngine
{
    #region 构造函数
        public CustomViewEngine() : this(null)
        {
        }
        public CustomViewEngine(IViewPageActivator viewPageActivator) : base(viewPageActivator)
        {
            this.SetEngine();
        }
        #endregion

    public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
        {
            if (controllerContext.HttpContext.Request.UserAgent.Contains("Chrome/74.0.3729.169"))
            {
                this.SetEngine("Chrome");
            }
            else
            {
                this.SetEngine();//一定得有,因为只有一个Engine实例
            }
            return base.FindView(controllerContext, viewName, masterName, useCache);
        }

    public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
        {
            if (controllerContext.HttpContext.Request.UserAgent.Contains("Chrome/74.0.3729.169"))
            {
                this.SetEngine("Chrome");
            }
            else
            {
                this.SetEngine();
            }
            return base.FindPartialView(controllerContext, partialViewName, useCache);
        }
    /// <summary>
    /// 把模板给换了
    /// </summary>
    /// <param name="browser"></param>
    private void SetEngine(string browser="")
        {
            base.AreaViewLocationFormats = new string[]
                        {
                "~/Areas/{2}/"+browser+"Views/{1}/{0}.cshtml",
                "~/Areas/{2}/"+browser+"Views/{1}/{0}.vbhtml",
                "~/Areas/{2}/"+browser+"Views/Shared/{0}.cshtml",
                "~/Areas/{2}/"+browser+"Views/Shared/{0}.vbhtml"
                        };
            base.AreaMasterLocationFormats = new string[]
            {
                "~/Areas/{2}/"+browser+"Views/{1}/{0}.cshtml",
                "~/Areas/{2}/"+browser+"Views/{1}/{0}.vbhtml",
                "~/Areas/{2}/"+browser+"Views/Shared/{0}.cshtml",
                "~/Areas/{2}/"+browser+"Views/Shared/{0}.vbhtml"
            };
            base.AreaPartialViewLocationFormats = new string[]
            {
                "~/Areas/{2}/"+browser+"Views/{1}/{0}.cshtml",
                "~/Areas/{2}/"+browser+"Views/{1}/{0}.vbhtml",
                "~/Areas/{2}/"+browser+"Views/Shared/{0}.cshtml",
                "~/Areas/{2}/"+browser+"Views/Shared/{0}.vbhtml"
            };
            base.ViewLocationFormats = new string[]
            {
                "~/"+browser+"Views/{1}/{0}.cshtml",
                "~/"+browser+"Views/{1}/{0}.vbhtml",
                "~/"+browser+"Views/Shared/{0}.cshtml",
                "~/"+browser+"Views/Shared/{0}.vbhtml"
            };
            base.MasterLocationFormats = new string[]
            {
                "~/"+browser+"Views/{1}/{0}.cshtml",
                "~/"+browser+"Views/{1}/{0}.vbhtml",
                "~/"+browser+"Views/Shared/{0}.cshtml",
                "~/"+browser+"Views/Shared/{0}.vbhtml"
            };
            base.PartialViewLocationFormats = new string[]
            {
                "~/"+browser+"Views/{1}/{0}.cshtml",
                "~/"+browser+"Views/{1}/{0}.vbhtml",
                "~/"+browser+"Views/Shared/{0}.cshtml",
                "~/"+browser+"Views/Shared/{0}.vbhtml"
            };
        }
}

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

posted @ 2020-03-13 23:31  明志德道  阅读(123)  评论(0编辑  收藏  举报