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

 public class RazorEngineExpand : RazorViewEngine
{
private void SetAdmin()
{
AreaPartialViewLocationFormats = new[]
{
"~/Admin/Views/{2}/{1}/Shared/{0}.cshtml",
"~/Admin/Views/{2}/Views/{1}/{0}.cshtml",
"~/Admin/Views/Shared/{0}.cshtml"
}; AreaViewLocationFormats = new[]
{
"~/Admin/Views/{2}/{1}/{0}.cshtml",
"~/Admin/Views/Shared/{0}.cshtml"
}; AreaMasterLocationFormats = new[]
{
"~/Admin/Views/{2}/Shared/{0}.cshtml",
"~/Admin/Views/Shared/{0}.cshtml",
"~/Views/Shared/{0}.cshtml"
}; } protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
{
return new RazorView(controllerContext, partialPath, layoutPath: null, runViewStartPages: false, viewStartFileExtensions: FileExtensions, viewPageActivator: ViewPageActivator);
} protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
var view = new RazorView(controllerContext, viewPath, layoutPath: masterPath, runViewStartPages: true, viewStartFileExtensions: FileExtensions, viewPageActivator: ViewPageActivator);
return view;
}
}

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

  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中看到的字符串数组:

        public string[] AreaMasterLocationFormats { get; set; }
public string[] AreaPartialViewLocationFormats { get; set; }
public string[] AreaViewLocationFormats { get; set; }
public string[] FileExtensions { get; set; }
public string[] MasterLocationFormats { get; set; }
public string[] PartialViewLocationFormats { get; set; }
public IViewLocationCache ViewLocationCache { get; set; }
public string[] ViewLocationFormats { get; set; }

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

  public virtual ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (String.IsNullOrEmpty(viewName))
{
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "viewName");
} string[] viewLocationsSearched;
string[] masterLocationsSearched; string controllerName = controllerContext.RouteData.GetRequiredString("controller");
string viewPath = GetPath(controllerContext, ViewLocationFormats, AreaViewLocationFormats, "ViewLocationFormats", viewName, controllerName, CacheKeyPrefixView, useCache, out viewLocationsSearched);
string masterPath = GetPath(controllerContext, MasterLocationFormats, AreaMasterLocationFormats, "MasterLocationFormats", masterName, controllerName, CacheKeyPrefixMaster, useCache, out masterLocationsSearched); if (String.IsNullOrEmpty(viewPath) || (String.IsNullOrEmpty(masterPath) && !String.IsNullOrEmpty(masterName)))
{
return new ViewEngineResult(viewLocationsSearched.Union(masterLocationsSearched));
} return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this);
}

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

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

 private string GetPath(ControllerContext controllerContext, string[] locations, string[] areaLocations, string locationsPropertyName, string name, string controllerName, string cacheKeyPrefix, bool useCache, out string[] searchedLocations)
{
searchedLocations = _emptyLocations; if (String.IsNullOrEmpty(name))
{
return String.Empty;
} string areaName = AreaHelpers.GetAreaName(controllerContext.RouteData);
bool usingAreas = !String.IsNullOrEmpty(areaName);
List<ViewLocation> viewLocations = GetViewLocations(locations, (usingAreas) ? areaLocations : null); if (viewLocations.Count == 0)
{
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
MvcResources.Common_PropertyCannotBeNullOrEmpty, locationsPropertyName));
} bool nameRepresentsPath = IsSpecificPath(name);
string cacheKey = CreateCacheKey(cacheKeyPrefix, name, (nameRepresentsPath) ? String.Empty : controllerName, areaName); if (useCache)
{
// Only look at cached display modes that can handle the context.
IEnumerable<IDisplayMode> possibleDisplayModes = DisplayModeProvider.GetAvailableDisplayModesForContext(controllerContext.HttpContext, controllerContext.DisplayMode);
foreach (IDisplayMode displayMode in possibleDisplayModes)
{
string cachedLocation = ViewLocationCache.GetViewLocation(controllerContext.HttpContext, AppendDisplayModeToCacheKey(cacheKey, displayMode.DisplayModeId)); if (cachedLocation == null)
{
// If any matching display mode location is not in the cache, fall back to the uncached behavior, which will repopulate all of our caches.
return null;
} // A non-empty cachedLocation indicates that we have a matching file on disk. Return that result.
if (cachedLocation.Length > 0)
{
if (controllerContext.DisplayMode == null)
{
controllerContext.DisplayMode = displayMode;
} return cachedLocation;
}
// An empty cachedLocation value indicates that we don't have a matching file on disk. Keep going down the list of possible display modes.
} // GetPath is called again without using the cache.
return null;
}
else
{
return nameRepresentsPath
? GetPathFromSpecificName(controllerContext, name, cacheKey, ref searchedLocations)
: GetPathFromGeneralName(controllerContext, viewLocations, name, controllerName, areaName, cacheKey, ref searchedLocations);
}
}

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

  private static bool IsSpecificPath(string name)
{
char c = name[0];
return (c == '~' || c == '/');
}

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

 private string GetPathFromSpecificName(ControllerContext controllerContext, string name, string cacheKey, ref string[] searchedLocations)
{
string result = name; if (!(FilePathIsSupported(name) && FileExists(controllerContext, name)))
{
result = String.Empty;
searchedLocations = new[] { name };
} ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, result);
return result;
}

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

private string GetPathFromGeneralName(ControllerContext controllerContext, List<ViewLocation> locations, string name, string controllerName, string areaName, string cacheKey, ref string[] searchedLocations)
{
string result = String.Empty;
searchedLocations = new string[locations.Count]; for (int i = 0; i < locations.Count; i++)
{
ViewLocation location = locations[i];
string virtualPath = location.Format(name, controllerName, areaName);
DisplayInfo virtualPathDisplayInfo = DisplayModeProvider.GetDisplayInfoForVirtualPath(virtualPath, controllerContext.HttpContext, path => FileExists(controllerContext, path), controllerContext.DisplayMode); if (virtualPathDisplayInfo != null)
{
string resolvedVirtualPath = virtualPathDisplayInfo.FilePath; searchedLocations = _emptyLocations;
result = resolvedVirtualPath;
ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, AppendDisplayModeToCacheKey(cacheKey, virtualPathDisplayInfo.DisplayMode.DisplayModeId), result); if (controllerContext.DisplayMode == null)
{
controllerContext.DisplayMode = virtualPathDisplayInfo.DisplayMode;
} // Populate the cache for all other display modes. We want to cache both file system hits and misses so that we can distinguish
// in future requests whether a file's status was evicted from the cache (null value) or if the file doesn't exist (empty string).
IEnumerable<IDisplayMode> allDisplayModes = DisplayModeProvider.Modes;
foreach (IDisplayMode displayMode in allDisplayModes)
{
if (displayMode.DisplayModeId != virtualPathDisplayInfo.DisplayMode.DisplayModeId)
{
DisplayInfo displayInfoToCache = displayMode.GetDisplayInfo(controllerContext.HttpContext, virtualPath, virtualPathExists: path => FileExists(controllerContext, path)); string cacheValue = String.Empty;
if (displayInfoToCache != null && displayInfoToCache.FilePath != null)
{
cacheValue = displayInfoToCache.FilePath;
}
ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, AppendDisplayModeToCacheKey(cacheKey, displayMode.DisplayModeId), cacheValue);
}
}
break;
} searchedLocations[i] = virtualPath;
} return result;
}

        这个方法里面我们首先来看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. VS2005打开VS2008 VS2010 VS2012

    我用vs2005较多,但网上找的代码经常是08 10 或者2012的,总结了以下技巧可以打开工程比较小巧的高版本代码. <1>用记事本打开解决方案文件“解决方案名.sln”,然后修改最上面 ...

  2. SendMessage 窗口函数

    函数功能:该函数将指定的消息发送到一个或多个窗口.此函数为指定的窗口调用窗口程序,直到窗口程序处理完消息再返回.而函数PostMessage不同,将一个消息寄送到一个线程的消息队列后立即返回. MSD ...

  3. hosts文件的作用 whois查询域名信息

      Whois查询域名信息 在操作系统中的路径:Window98—在Windows目录下Windows 2000/XP—在C:\WINDOWS\system32\drivers\etc目录下 内容:包 ...

  4. u3d 2d序列动画代码

    using UnityEngine; using System.Collections; public class AniSprite : MonoBehaviour { private float ...

  5. 《WPF程序设计指南》读书笔记——第9章 路由输入事件

    1.使用路由事件 路由事件是一种可以针对元素树中的多个侦听器(而不是仅针对引发该事件的对象)调用处理程序的事件.通俗地说,路由事件会在可视树(逻辑树是其子集)上,上下routed,如果哪个节点上订阅了 ...

  6. Linux 目录操作和4中文件拷贝效率测试

    /*1.用户输入任意目录名称,显示该目录下的文件列表信息,包括文件类型,文件权限,文件大小,文件名称2.拷贝用户输入的文件到当前目录下3.第二点功能,使用4种方式完成,并比较说明效率*/ /* str ...

  7. OFBiz进阶之HelloWorld(五)创建新实体

    参考文档 https://cwiki.apache.org/confluence/display/OFBIZ/OFBiz+Tutorial+-+A+Beginners+Development+Guid ...

  8. 新的MOVE结构,和在项目中实际的感受

    关于MVC/MVP的瑕疵 MVC 和 MVP是最简单,最脍炙人口的框架结构. 有一段时间, 凡事有一定规模的代码,我都会架在上面,甚至后台程序也不例外(预留出可以注册的用户交互接口,作为后台控制器). ...

  9. Datatables中文API——回调函数

    fnCookieCallback:还没有使用过 $(document).ready(function () { $('#example').dataTable({ "fnCookieCall ...

  10. nginx流量带宽等请求状态统计( ngx_req_status)

    介绍 ngx_req_status用来展示nginx请求状态信息,类似于apache的status,nginx自带的模块只能显示连接数等等信息,我们并不能知道到底有哪些请求.以及各url域名所消耗的带 ...