定制属于你自己的ViewEngine(一套逻辑多套UI)
ASP.NET MVC出来这么久了,心中却又很多的疑惑:为什么所有的View都要放在Views目录下? 为什么Shared文件夹下面的页面可以被共享? 为什么Page既可以是*.cshtml,也可以是*.aspx?
其实上面的几个问题归结起来都是视图引擎的功效。
在传统的ASP.NET中,可能还没有ViewEngine的概念。因为在Web From里面,实现Page实现了IHttpHanlder的接口,所以Page既是响应的处理类,也是视图的渲染类。在ASP.NET MVC中,视图的概念被抽象了出来,试图引擎的概念也被抽象成了一个接口。
首先来看一下IViewEngine接口的定义
1 namespace System.Web.Mvc 2 { 3 public interface IViewEngine 4 { 5 ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache); 6 ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache); 7 void ReleaseView(ControllerContext controllerContext, IView view); 8 } 9 }
总共3个函数,总结起来大概就是两个功能:Find & Release。
默认情况下,ASP.NET MVC提供了两个视图引擎:WebFormViewEngine和RazorViewEngine
1 namespace System.Web.Mvc 2 { 3 public static class ViewEngines 4 { 5 private static readonly ViewEngineCollection _engines = new ViewEngineCollection 6 { 7 new WebFormViewEngine(), 8 new RazorViewEngine(), 9 }; 10 11 public static ViewEngineCollection Engines 12 { 13 get { return _engines; } 14 } 15 } 16 }
这就是为什么ASP.NET MVC既支持*.aspx,又支持*.cshtml的原因了(个人觉得如果已经确定要使用RazorView的话,不如把WebFormViewEngine给移除,可能对性能会有所帮助)。
那为什么所有的视图都要放在Views目录下呢,这个就要拜RazorViewngines所赐了。
下面是RazorViewEngine的构造函数:
1 public RazorViewEngine(IViewPageActivator viewPageActivator) 2 : base(viewPageActivator) 3 { 4 AreaViewLocationFormats = new[] 5 { 6 "~/Areas/{2}/Views/{1}/{0}.cshtml", 7 "~/Areas/{2}/Views/{1}/{0}.vbhtml", 8 "~/Areas/{2}/Views/Shared/{0}.cshtml", 9 "~/Areas/{2}/Views/Shared/{0}.vbhtml" 10 }; 11 AreaMasterLocationFormats = new[] 12 { 13 "~/Areas/{2}/Views/{1}/{0}.cshtml", 14 "~/Areas/{2}/Views/{1}/{0}.vbhtml", 15 "~/Areas/{2}/Views/Shared/{0}.cshtml", 16 "~/Areas/{2}/Views/Shared/{0}.vbhtml" 17 }; 18 AreaPartialViewLocationFormats = new[] 19 { 20 "~/Areas/{2}/Views/{1}/{0}.cshtml", 21 "~/Areas/{2}/Views/{1}/{0}.vbhtml", 22 "~/Areas/{2}/Views/Shared/{0}.cshtml", 23 "~/Areas/{2}/Views/Shared/{0}.vbhtml" 24 }; 25 26 ViewLocationFormats = new[] 27 { 28 "~/Views/{1}/{0}.cshtml", 29 "~/Views/{1}/{0}.vbhtml", 30 "~/Views/Shared/{0}.cshtml", 31 "~/Views/Shared/{0}.vbhtml" 32 }; 33 MasterLocationFormats = new[] 34 { 35 "~/Views/{1}/{0}.cshtml", 36 "~/Views/{1}/{0}.vbhtml", 37 "~/Views/Shared/{0}.cshtml", 38 "~/Views/Shared/{0}.vbhtml" 39 }; 40 PartialViewLocationFormats = new[] 41 { 42 "~/Views/{1}/{0}.cshtml", 43 "~/Views/{1}/{0}.vbhtml", 44 "~/Views/Shared/{0}.cshtml", 45 "~/Views/Shared/{0}.vbhtml" 46 }; 47 48 FileExtensions = new[] 49 { 50 "cshtml", 51 "vbhtml", 52 }; 53 }
所有的寻址路径都被格式化了,是不是很眼熟呢,关于这里为啥用数组而不用List,个人觉得,数组的寻址效率要更高些,遍历速度更快。
好了,找了“罪魁祸首”,就好好地调教一个,让它乖乖听话,小样让去哪就去哪里。
1 /// <summary> 2 /// razor视图引擎扩展 3 /// </summary> 4 public class CustomerViewEngine : RazorViewEngine 5 { 6 /// <summary> 7 /// 可以分开部署不同语种 8 /// </summary> 9 /// <param name="engineName"></param> 10 public CustomerViewEngine(string engineName) 11 { 12 base.ViewLocationFormats = new[] 13 { 14 "~/Views" + engineName + "/{1}/{0}.cshtml", 15 "~/Views" + engineName + "/Shared/{0}.cshtml" 16 }; 17 18 base.PartialViewLocationFormats = new[] 19 { 20 "~/Views" + engineName + "/{1}/{0}.cshtml", 21 "~/Views" + engineName + "/Shared/{0}.cshtml" 22 }; 23 24 base.AreaViewLocationFormats = new[] 25 { 26 "~Areas/{2}/Views" + engineName + "/{1}/{0}.cshtml", 27 "~Areas/{2}/Views" + engineName + "/Shared/{0}.cshtml" 28 }; 29 30 base.AreaPartialViewLocationFormats = new[] 31 { 32 "~Areas/{2}/Views" + engineName + "/{1}/{0}.cshtml", 33 "~Areas/{2}/Views" + engineName + "/Shared/{0}.cshtml" 34 }; 35 } 36 37 public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) 38 { 39 this.SetEngine(controllerContext); 40 return base.FindView(controllerContext, viewName, masterName, useCache); 41 } 42 43 public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache) 44 { 45 this.SetEngine(controllerContext); 46 return base.FindPartialView(controllerContext, partialViewName, useCache); 47 } 48 49 /// <summary> 50 /// 根据条件自行设置,目前是chrome浏览器就展示默认的 51 /// 不是chrome浏览器的话就展示/Themes/Eleven下的 52 /// 可以直接测试是移动端还是pc端 53 /// 然后写入cookie 54 /// </summary> 55 private void SetEngine(ControllerContext controllerContext) 56 { 57 string engineName = "/Themes/Eleven"; 58 if (controllerContext.HttpContext.Request.UserAgent.IndexOf("Chrome/65") >= 0) 59 { 60 engineName = null; 61 } 62 63 //if (controllerContext.HttpContext.Request.IsMobile())//检测是不是移动端 64 //{ 65 // engineName = null; 66 //} 67 68 base.ViewLocationFormats = new[] 69 { 70 "~/Views" + engineName + "/{1}/{0}.cshtml", 71 "~/Views" + engineName + "/Shared/{0}.cshtml" 72 }; 73 74 base.PartialViewLocationFormats = new[] 75 { 76 "~/Views" + engineName + "/{1}/{0}.cshtml", 77 "~/Views" + engineName + "/Shared/{0}.cshtml" 78 }; 79 80 base.AreaViewLocationFormats = new[] 81 { 82 "~Areas/{2}/Views" + engineName + "/{1}/{0}.cshtml", 83 "~Areas/{2}/Views" + engineName + "/Shared/{0}.cshtml" 84 }; 85 86 base.AreaPartialViewLocationFormats = new[] 87 { 88 "~Areas/{2}/Views" + engineName + "/{1}/{0}.cshtml", 89 "~Areas/{2}/Views" + engineName + "/Shared/{0}.cshtml" 90 }; 91 }
接下去就很简单了,只需要把原来的视图引擎清空,加载自己的视图引擎就可以了
1 protected void Application_Start() 2 { 3 AreaRegistration.RegisterAllAreas(); 4 5 ViewEngines.Engines.Clear(); 6 7 ViewEngines.Engines.Add(new CustomViewEngine()); 8 9 RegisterGlobalFilters(GlobalFilters.Filters); 10 RegisterRoutes(RouteTable.Routes); 11 }