基于Web Form引擎的 WebFormViewEngine 和针对Razor引擎的 RazorViewEngine 都是抽象类型 BuildManagerViewEngine 的子类,而后者又继承自 VirtualPathProviderViewEngine 。在这里我们仅仅对实现在RazorViewEngine中View获取的逻辑进行简单介绍。由于Razor引擎下的View通过RazorView对象来表示,而RazorView通过View文件的虚拟路径来构建,所以RazorViewEngine的View获取机制在于根据当前上下文找到与指定View名称相匹配的View文件(.cshtml或者.vbhtml文件),然后根据该 View文件的虚拟路径创建一个RazorView对象并最终封装成ViewEngineResult对象返回。[本文已经同步到《 How ASP.NET MVC Works? 》中]

实现在RazorViewEngine中的目标View文件的搜索是根据一个预定义顺序进行的。如果当前请求不是针对某个Area的,下面的列表代表了View的搜索顺序:

  • ~/Views/{ControllerName}/{ViewName}.cshtml
  • ~/Views/{ControllerName}/{ViewName}.vbhtml
  • ~/Views/Shared/{ViewName}.cshtml
  • ~/Views/Shared/{ViewName}.vbhtml
  • 对于针对某个Area的请求,RazorViewEngine会先按照如下的顺序对目标View进行搜索。如果在这个列表中没有成功找到目标View文件,会继续按照上面的属性进行搜索。

  • ~/Areas/{AreaName}/Views/{ControllerName}/{ViewName}.cshtml
  • ~/Areas/{AreaName}/Views/{ControllerName}/{ViewName}.vbhtml
  • ~/Areas/{AreaName}/Views/ Shared /{ViewName}.cshtml
  • ~/Areas/{AreaName}/Views/ Shared /{ViewName}.vbhtml
  • 如果按照上面的搜索顺序依然找不目标View文件,RazorViewEngine会根据这个列表创建并返回一个ViewEngineResult对象。这里介绍的View搜索机制不仅仅应用于普通的View文件,还应用于Partial View和布局文件的搜索。

    ViewEngine不仅仅通过FindView/FindPartialView根据当前上下文获取指定的View,还通过ReleaseView对指定的View进行释放回收操作。ReleaseView方法在RazorViewEngine的实现很简单,如果指定的View对象的类型实现IDispose接口,它会直接调用其Dispose方法。下图所示的UML体现了Razor引擎涉及的相关类型/接口以及它们之间的相互关系。

    在《 ASP.NET MVC的Razor引擎:RazorView 》一文中我们创建了一个用于模拟RazorView的SimpleRazorView,现在我们为它创建一个对应的RazorViewEngine,我们直接在该实例项目中添加如下一个SimpleRazorViewEngine。
       1: public class SimpleRazorViewEngine: IViewEngine
       3:     private string[] viewLocationFormats = new string[] { 
       4:         "~/Views/{1}/{0}.cshtml", 
       5:         "~/Views/{1}/{0}.vbhtml", 
       6:         "~/Views/Shared/{0}.cshtml", 
       7:         "~/Views/Shared/{0}.vbhtml" };
       8:     private string[] areaViewLocationFormats = new string[] { 
       9:         "~/Areas/{2}/Views/{1}/{0}.cshtml", 
      10:         "~/Areas/{2}/Views/{1}/{0}.vbhtml", 
      11:         "~/Areas/{2}/Views/Shared/{0}.cshtml", 
      12:         "~/Areas/{2}/Views/Shared/{0}.vbhtml" };
      14:     public ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
      15:     {
      16:         return FindView(controllerContext, partialViewName, null, useCache);
      17:     }
      19:     public ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
      20:     {
      21:         string controllerName = controllerContext.RouteData.GetRequiredString("controller");
      22:         object areaName;
      23:         List<string> viewLocations = new List<string>();
      24:         Array.ForEach(viewLocationFormats, format => viewLocations.Add(string.Format(format, viewName, controllerName)));
      25:         if (controllerContext.RouteData.Values.TryGetValue("area", out areaName))
      26:         {
      27:             Array.ForEach(areaViewLocationFormats, format=>viewLocations.Add(string.Format(format,viewName,controllerName, areaName)));
      28:         }
      29:         foreach (string viewLocation in viewLocations)
      30:         {
      31:             string filePath = controllerContext.HttpContext.Request.MapPath(viewLocation);
      32:             if (File.Exists(filePath))
      33:             {
      34:                 return new ViewEngineResult(new SimpleRazorView(viewLocation), 
      35:                     this);
      36:             }
      37:         }
      38:         return new ViewEngineResult(viewLocations);
      39:     }
      41:     public void ReleaseView(ControllerContext controllerContext, IView view)
      42:     {
      43:         IDisposable disposable = view as IDisposable;
      44:         if (null != disposable)
      45:         {
      46:             disposable.Dispose();
      47:         }
      48:     }
      49: }
    我们完全按照上面介绍的路径顺序搜索指定的目标View。简单起见,我们在对目标View进行搜索时忽略了指定的布局文件名和对ViewEngineResult的缓存。这个自定义的SimpleRazorViewEngine在Global.asax中通过如下的代码对进行注册。
       1: public class MvcApplication : System.Web.HttpApplication
       3:     protected void Application_Start()
       5:         //其他操作
       6:         ViewEngines.Engines.Clear();
       7:         ViewEngines.Engines.Add(new SimpleRazorViewEngine());

    我们定义了如下一个HomeController:

       1: public class HomeController : Controller
       3:     public ActionResult Index()
       5:         Contact contact = new Contact 
       7:              Name             = "张三", 
       8:              PhoneNo          = "123456789", 
       9:              EmailAddress     = "zhangsan@gmail.com" 
      10:         };
      11:         return View(contact);
      12:     }
      13: }
      15: public class Contact
      16: {
      17:     [DisplayName("姓名")]
      18:     public string Name { get; set; }
      20:     [DisplayName("电话号码")]
      21:     public string PhoneNo { get; set; }
      23:     [DisplayName("电子邮箱地址")]
      24:     public string EmailAddress { get; set; }
      25: }

    我们的View(“~/Views/Home/Index.cshtml”)很简单。如下面的代码片断所示,这是一个Model类型为Contact的强类型View,在该View中我们直接调用HtmlHelper<TModel>的扩展方法EditorForModel将作为Model的Contact对象以编辑模式呈现在一个表单之中。

       1: @model Contact
       2: @{
       3:     ViewBag.Title = Model.Name;
       6: @using (Html.BeginForm())
       8:     @Html.EditorForModel()
       9:     <input type="submit" value="保存" />
      10: }

    为了验证我们自定义的SimpleRazorView对布局文件和_ViewStart页面的支持,我们在“~/Views/Shared/”目录下定义了如下一个名为“_Layout.cshtml”的布局文件。布局文件的设置通过定义在“~/Views/”目录下具有如下定义的“_ViewStart.cshtml”文件来指定。

       1: _Layout.cshtml:
       2: <html>
       3:     <head>
       4:         <title>@ViewBag.Title </title>
       5:     </head>    
       6:     <body>
       7:         <h3>编辑联系人信息</h3>
       8:         @RenderBody()
       9:     </body>
      10: </html>
      12: _ViewStart.cshtml:
      13: @{
      14:     Layout = "~/Views/Shared/_Layout.cshtml";
      15: }

    运行我们的程序后会在浏览器中呈现如下图所示的输出结果,可以看出这和我们直接在Action方法Index中返回一个ViewResult对象没有什么不同。

    ASP.NET MVC的Razor引擎:View编译原理
    ASP.NET MVC的Razor引擎:RazorView
    ASP.NET MVC的Razor引擎:IoC在View激活过程中的应用
    ASP.NET MVC的Razor引擎:RazorViewEngine

    作者:蒋金楠
    微信公众账号:大内老A
    微博: www.weibo.com/artech
    如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体 将会停用)。
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
    ASP.Net Core Razor+AdminLTE 小试牛刀(下)
    一个基于 bootstrap 的轻量级后台模板,这个前端界面个人感觉很清爽,对于一个大后端的我来说,可以减少较多的时间去承担前端的工作但又必须去独立去完成一个后台系统开发的任务,并且,文档还算比较齐全,对着demo可以完成一个基本的前端框架搭建了。大家如有更为好看的又方便后端上手的前端框架,也可以在留言区分享一下呗。
    ASP.Net Core Razor+AdminLTE 小试牛刀(上)
    一个基于 bootstrap 的轻量级后台模板,这个前端界面个人感觉很清爽,对于一个大后端的我来说,可以减少较多的时间去承担前端的工作但又必须去独立去完成一个后台系统开发的任务,并且,文档还算比较齐全,对着demo可以完成一个基本的前端框架搭建了。大家如有更为好看的又方便后端上手的前端框架,也可以在留言区分享一下呗。
    原文:学习ASP.NET Core Razor 编程系列十五——文件上传功能(三) 学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.
    原文:学习ASP.NET Core Razor 编程系列十四——文件上传功能(二) 学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.
    原文:学习ASP.NET Core Razor 编程系列十二——在页面中增加校验 学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.