很多时候在mvc项目中我们需要去扩展自己的视图引擎,大概看起来应该下面这个样子的:

  1. public class RazorEngineExpand : RazorViewEngine
  2. {
  3. private void SetAdmin()
  4. {
  5. AreaPartialViewLocationFormats = new[]
  6. {
  7. "~/Admin/Views/{2}/{1}/Shared/{0}.cshtml",
  8. "~/Admin/Views/{2}/Views/{1}/{0}.cshtml",
  9. "~/Admin/Views/Shared/{0}.cshtml"
  10. };
  11.  
  12. AreaViewLocationFormats = new[]
  13. {
  14. "~/Admin/Views/{2}/{1}/{0}.cshtml",
  15. "~/Admin/Views/Shared/{0}.cshtml"
  16. };
  17.  
  18. AreaMasterLocationFormats = new[]
  19. {
  20. "~/Admin/Views/{2}/Shared/{0}.cshtml",
  21. "~/Admin/Views/Shared/{0}.cshtml",
  22. "~/Views/Shared/{0}.cshtml"
  23. };
  24.  
  25. }
  26.  
  27. protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
  28. {
  29. return new RazorView(controllerContext, partialPath, layoutPath: null, runViewStartPages: false, viewStartFileExtensions: FileExtensions, viewPageActivator: ViewPageActivator);
  30. }
  31.  
  32. protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
  33. {
  34. var view = new RazorView(controllerContext, viewPath, layoutPath: masterPath, runViewStartPages: true, viewStartFileExtensions: FileExtensions, viewPageActivator: ViewPageActivator);
  35. return view;
  36. }
  37. }

  我们要扩展自己的Razor视图引擎,则要继承自RazorViewEngine,并重写里面的相关的方法,RazorViewEngine最终实现的是IViewEngine接口,但是IViewEngine接口里面的方法:

  1.   public interface IViewEngine
        {
            ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache);
            ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache);
            void ReleaseView(ControllerContext controllerContext, IView view);
        }

    两个寻找视图的方法和一个释放视图资源的方法。根据传入的参数,大体的实现,我们几乎可以猜到。在抽象类VirtualPathProviderViewEngine中实现了这三个方法,我们注意到在该类中可以看到一些在RazorViewEngine中看到的字符串数组:

  1. public string[] AreaMasterLocationFormats { get; set; }
  2. public string[] AreaPartialViewLocationFormats { get; set; }
  3. public string[] AreaViewLocationFormats { get; set; }
  4. public string[] FileExtensions { get; set; }
  5. public string[] MasterLocationFormats { get; set; }
  6. public string[] PartialViewLocationFormats { get; set; }
  7. public IViewLocationCache ViewLocationCache { get; set; }
  8. public string[] ViewLocationFormats { get; set; }

  这些属性最终会在RazorViewEngine中得到初始化。我们主要来看FindView方法,FindPartialView方法的具体实现和FindView无多大区别,我们只介绍FindView即可:

  1. public virtual ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
  2. {
  3. if (controllerContext == null)
  4. {
  5. throw new ArgumentNullException("controllerContext");
  6. }
  7. if (String.IsNullOrEmpty(viewName))
  8. {
  9. throw new ArgumentException(MvcResources.Common_NullOrEmpty, "viewName");
  10. }
  11.  
  12. string[] viewLocationsSearched;
  13. string[] masterLocationsSearched;
  14.  
  15. string controllerName = controllerContext.RouteData.GetRequiredString("controller");
  16. string viewPath = GetPath(controllerContext, ViewLocationFormats, AreaViewLocationFormats, "ViewLocationFormats", viewName, controllerName, CacheKeyPrefixView, useCache, out viewLocationsSearched);
  17. string masterPath = GetPath(controllerContext, MasterLocationFormats, AreaMasterLocationFormats, "MasterLocationFormats", masterName, controllerName, CacheKeyPrefixMaster, useCache, out masterLocationsSearched);
  18.  
  19. if (String.IsNullOrEmpty(viewPath) || (String.IsNullOrEmpty(masterPath) && !String.IsNullOrEmpty(masterName)))
  20. {
  21. return new ViewEngineResult(viewLocationsSearched.Union(masterLocationsSearched));
  22. }
  23.  
  24. return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this);
  25. }

   我们可以看到return的语句可以发现最终是将查找到的view经过一些列的处理包装成ViewEngineResult对象返回了。我们逐一来看,首先获取到控制器,获取view文件的虚拟路径和母版文件的虚拟路径,然后根据这些参数,主要是view文件的虚拟路径来创建RazorView对象,最后封装成ViewEngineResult对象返回。

这里我们主要来看一下是如何获取到view文件的虚拟路径的,我们来看GetPath方法的具体实现:

  1. private string GetPath(ControllerContext controllerContext, string[] locations, string[] areaLocations, string locationsPropertyName, string name, string controllerName, string cacheKeyPrefix, bool useCache, out string[] searchedLocations)
  2. {
  3. searchedLocations = _emptyLocations;
  4.  
  5. if (String.IsNullOrEmpty(name))
  6. {
  7. return String.Empty;
  8. }
  9.  
  10. string areaName = AreaHelpers.GetAreaName(controllerContext.RouteData);
  11. bool usingAreas = !String.IsNullOrEmpty(areaName);
  12. List<ViewLocation> viewLocations = GetViewLocations(locations, (usingAreas) ? areaLocations : null);
  13.  
  14. if (viewLocations.Count == 0)
  15. {
  16. throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
  17. MvcResources.Common_PropertyCannotBeNullOrEmpty, locationsPropertyName));
  18. }
  19.  
  20. bool nameRepresentsPath = IsSpecificPath(name);
  21. string cacheKey = CreateCacheKey(cacheKeyPrefix, name, (nameRepresentsPath) ? String.Empty : controllerName, areaName);
  22.  
  23. if (useCache)
  24. {
  25. // Only look at cached display modes that can handle the context.
  26. IEnumerable<IDisplayMode> possibleDisplayModes = DisplayModeProvider.GetAvailableDisplayModesForContext(controllerContext.HttpContext, controllerContext.DisplayMode);
  27. foreach (IDisplayMode displayMode in possibleDisplayModes)
  28. {
  29. string cachedLocation = ViewLocationCache.GetViewLocation(controllerContext.HttpContext, AppendDisplayModeToCacheKey(cacheKey, displayMode.DisplayModeId));
  30.  
  31. if (cachedLocation == null)
  32. {
  33. // If any matching display mode location is not in the cache, fall back to the uncached behavior, which will repopulate all of our caches.
  34. return null;
  35. }
  36.  
  37. // A non-empty cachedLocation indicates that we have a matching file on disk. Return that result.
  38. if (cachedLocation.Length > 0)
  39. {
  40. if (controllerContext.DisplayMode == null)
  41. {
  42. controllerContext.DisplayMode = displayMode;
  43. }
  44.  
  45. return cachedLocation;
  46. }
  47. // An empty cachedLocation value indicates that we don't have a matching file on disk. Keep going down the list of possible display modes.
  48. }
  49.  
  50. // GetPath is called again without using the cache.
  51. return null;
  52. }
  53. else
  54. {
  55. return nameRepresentsPath
  56. ? GetPathFromSpecificName(controllerContext, name, cacheKey, ref searchedLocations)
  57. : GetPathFromGeneralName(controllerContext, viewLocations, name, controllerName, areaName, cacheKey, ref searchedLocations);
  58. }
  59. }

       首先判断是否使用了区域功能,并以此判断作为条件获取到所有的view的位置集合。然后看到一个是否使用了缓存功能,应该是对查找的view的虚拟路径进行的缓存,如果是的话,直接从缓存中获取要得到的view的虚拟路径。我们跳过最后,看到他返回的是什么?根据nameRepresentsPath调用不同的方法,nameRepresentsPath是由IsSpecificPath返回:

  1. private static bool IsSpecificPath(string name)
  2. {
  3. char c = name[0];
  4. return (c == '~' || c == '/');
  5. }

 了解之前首先的知道传入的name代表的是什么?追朔到FindView中我们可以发现name代表的就是viewName或是masterName。根据viewName来判断他是否是一个特殊的路径?感觉有点奇怪,应该说这个viewName和path应该都是指路径的意思。因此我们可以解释说是根据传入的路径来判断是虚拟路径还是绝对路径。然后我们再根据GetPathFromSpecificName方法的实现:

  1. private string GetPathFromSpecificName(ControllerContext controllerContext, string name, string cacheKey, ref string[] searchedLocations)
  2. {
  3. string result = name;
  4.  
  5. if (!(FilePathIsSupported(name) && FileExists(controllerContext, name)))
  6. {
  7. result = String.Empty;
  8. searchedLocations = new[] { name };
  9. }
  10.  
  11. ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, result);
  12. return result;
  13. }

    这个方法直接就是将name给返回去,也就是我们前面所说的view绝对路径了---nameRepresentsPath(代表路径嘛)GetPathFromGeneralName方法的实现:

  1. private string GetPathFromGeneralName(ControllerContext controllerContext, List<ViewLocation> locations, string name, string controllerName, string areaName, string cacheKey, ref string[] searchedLocations)
  2. {
  3. string result = String.Empty;
  4. searchedLocations = new string[locations.Count];
  5.  
  6. for (int i = 0; i < locations.Count; i++)
  7. {
  8. ViewLocation location = locations[i];
  9. string virtualPath = location.Format(name, controllerName, areaName);
  10. DisplayInfo virtualPathDisplayInfo = DisplayModeProvider.GetDisplayInfoForVirtualPath(virtualPath, controllerContext.HttpContext, path => FileExists(controllerContext, path), controllerContext.DisplayMode);
  11.  
  12. if (virtualPathDisplayInfo != null)
  13. {
  14. string resolvedVirtualPath = virtualPathDisplayInfo.FilePath;
  15.  
  16. searchedLocations = _emptyLocations;
  17. result = resolvedVirtualPath;
  18. ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, AppendDisplayModeToCacheKey(cacheKey, virtualPathDisplayInfo.DisplayMode.DisplayModeId), result);
  19.  
  20. if (controllerContext.DisplayMode == null)
  21. {
  22. controllerContext.DisplayMode = virtualPathDisplayInfo.DisplayMode;
  23. }
  24.  
  25. // Populate the cache for all other display modes. We want to cache both file system hits and misses so that we can distinguish
  26. // in future requests whether a file's status was evicted from the cache (null value) or if the file doesn't exist (empty string).
  27. IEnumerable<IDisplayMode> allDisplayModes = DisplayModeProvider.Modes;
  28. foreach (IDisplayMode displayMode in allDisplayModes)
  29. {
  30. if (displayMode.DisplayModeId != virtualPathDisplayInfo.DisplayMode.DisplayModeId)
  31. {
  32. DisplayInfo displayInfoToCache = displayMode.GetDisplayInfo(controllerContext.HttpContext, virtualPath, virtualPathExists: path => FileExists(controllerContext, path));
  33.  
  34. string cacheValue = String.Empty;
  35. if (displayInfoToCache != null && displayInfoToCache.FilePath != null)
  36. {
  37. cacheValue = displayInfoToCache.FilePath;
  38. }
  39. ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, AppendDisplayModeToCacheKey(cacheKey, displayMode.DisplayModeId), cacheValue);
  40. }
  41. }
  42. break;
  43. }
  44.  
  45. searchedLocations[i] = virtualPath;
  46. }
  47.  
  48. return result;
  49. }

        这个方法里面我们首先来看searchedLocations这个字符数组变量,由virtualPath赋值,virtualPath里面存储的就是有viewName,controllerName和areaName(假如有的话)构成的虚拟路径。因此searchedLocations数组里面存储的就是查找view的时候搜索过的所有路径集合。类似于一下这张图所示,代表着搜索view时的查找“路线”:

我们看返回的result,实际上代表的就是view的虚拟路径。因此我们可以总结一下这里面查找view做的事情:网上有位仁兄有个很好的总结:

  1. 获取视图位置(GetViewLocations)

    • 检查是否使用了区域(Area)
    • 如果使用了区域,则把areaLocations传入
    • GetViewLocations方法会将locations和areaLocations这两个字符串数组包装和合并成一个ViewLocation的集合
    • 如果集合没有东西,那么抛异常
  2. 缓存检索
  3. 获取路径
    • 如果名称像是一个绝对路径("/"或"~"开头)

      • 检查虚拟路径所指向的文件是否存在(FileExists)
      • 存在则返回名称(当作路径)。
      • 否则返回空字符串。
    • 如果名称不像是一个绝对路径
      • 遍历所有的视图位置生成虚拟路径
      • 如果虚拟路径所指向的文件存在,则返回这个虚拟路径。
      • 如果所有生成的虚拟路径所指向的文件都不存在,则返回空字符串。

缓存处理部分我并不关心,现在从外部来看GetPath方法,那么它的参数分为三大部分:

  • 缓存部分

    • controllerContext(主要利用里面的HttpContext.Cache模块)
    • cacheKeyPrefix
    • useCache
  • 位置部分:
    • locations和areaLocations,这是虚拟路径的模版,使用的值是VirtualPathProviderViewEngine的公开属性。
    • locationsPropertyName,这个用于抛异常的时候指示使用的哪个Property。
  • 名称部分:
    • name,这个参数会是viewName或者masterName
    • controllerName,这个参数标识了控制器的名称
    • areaName,没有出现在参数中,但利用controllerContext提取了出来,事实上controllerName也是从controllerContext中提取的,性质一样。

mvc源码解读(20)-controller和view之查找view的更多相关文章

  1. jvm源码解读--20 结合jvm源码理解 java 设计模式 模板方法

    write by 张艳涛 前言: 在学习jvm之前,看过设计模式的书,知道模板方法的设计模式,今天在看java并发编程的艺术里面关于AbstractQueuedSynchronizer 用法,这个就使 ...

  2. DRF(1) - REST、DRF(View源码解读、APIView源码解读)

    一.REST 1.什么是编程? 数据结构和算法的结合. 2.什么是REST? 首先回顾我们曾经做过的图书管理系统,我们是这样设计url的,如下: /books/ /get_all_books/ 访问所 ...

  3. REST、DRF(View源码解读、APIView源码解读)

    一 . REST            前言 1 . 编程 : 数据结构和算法的结合 .小程序如简单的计算器,我们输入初始数据,经过计算,得到最终的数据,这个过程中,初始数据和结果数据都是数据,而计算 ...

  4. Restful 1 -- REST、DRF(View源码解读、APIView源码解读)及框架实现

    一.REST 1.什么是编程? 数据结构和算法的结合 2.什么是REST? - url用来唯一定位资源,http请求方式来区分用户行为 首先回顾我们曾经做过的图书管理系统,我们是这样设计url的,如下 ...

  5. spring IOC DI AOP MVC 事务, mybatis 源码解读

    demo https://gitee.com/easybao/aop.git spring DI运行时序 AbstractApplicationContext类的 refresh()方法 1: pre ...

  6. AFNetworking 3.0 源码解读(九)之 AFNetworkActivityIndicatorManager

    让我们的APP像艺术品一样优雅,开发工程师更像是一名匠人,不仅需要精湛的技艺,而且要有一颗匠心. 前言 AFNetworkActivityIndicatorManager 是对状态栏中网络激活那个小控 ...

  7. AFNetworking 3.0 源码解读(三)之 AFURLRequestSerialization

    这篇就讲到了跟请求相关的类了 关于AFNetworking 3.0 源码解读 的文章篇幅都会很长,因为不仅仅要把代码进行详细的的解释,还会大概讲解和代码相关的知识点. 上半篇: URI编码的知识 关于 ...

  8. MVC系列——MVC源码学习:打造自己的MVC框架(四:了解神奇的视图引擎)

    前言:通过之前的三篇介绍,我们基本上完成了从请求发出到路由匹配.再到控制器的激活,再到Action的执行这些个过程.今天还是趁热打铁,将我们的View也来完善下,也让整个系列相对完整,博主不希望烂尾. ...

  9. MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)

    前言:上篇介绍了下自己的MVC框架前两个版本,经过两天的整理,版本三基本已经完成,今天还是发出来供大家参考和学习.虽然微软的Routing功能已经非常强大,完全没有必要再“重复造轮子”了,但博主还是觉 ...

随机推荐

  1. ECSHOP 支付宝发货确认接口,记录支付宝返回的交易号

    1,在order_info 数据表尾添加trade_no 字段 数据表尾怎么添加trade_no 字段 ECSHOP为了支付宝发货确认接口,需要记录支付宝返回的交易号 1,在order_info 数据 ...

  2. CodeBlocks去掉拼写检查

    打开: 选择Compiler... 将红框里面的勾都点掉即可!

  3. angularApi网站用vue重构

    最近在博客园上看到不少关于vue的文章但感觉都是在简单原生写法上,真正vue在实际开发中的优点组件化,spa应用,路由好像都没涉及到,我在学angular1的时候发现没有中文版的api,于是本人不才弄 ...

  4. python学习笔记6(字典)

    映射:键值对的关系,键(key)映射值(value) 字典是Python唯一的映射类型 >>> phonebook = {'} >>> phonebook {'} ...

  5. ExtJs4.2 知识点

    知识点1:修改密码类 参考:点击这里 Ext.apply(Ext.form.VTypes, { password: function (val, field) { if (field.initialP ...

  6. why you write code so slow.

    今天我们要写一个日历表,用以存储所有的节假日. 虽然这个表设计的并不是很妙.但是将就着继续了. 让小弟把该表数据初始化3-5年的,结果一上午还没有出来,着急了,自己写了一个初始化的工具. 分享出来. ...

  7. java中四种引用类型

    java中四种引用类型  今天看代码,里面有一个类java.lang.ref.SoftReference把小弟弄神了,试想一下,接触java已经有3年了哇,连lang包下面的类都不了解,怎么混.后来在 ...

  8. C#查找子串在原串中出现次数

    提供的是一种思路,和具体语言无关. string test = "good good study day day up"; string r = test.Replace(&quo ...

  9. The7th Zhejiang Provincial Collegiate Programming Contest->Problem A:A - Who is Older?

    http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3322 可以看样例猜题意的水题. #include<bits/stdc ...

  10. zoj 3365

    题意 给你一个序列  改变尽可能少的数使其成为公差为一 递增的等差数列 可以将给你的序列减去一个等差数列 即num[i] -= i,若得到的数全部相等, 则说明给你的序列本身就满足条件  则只要寻求n ...