mvc源码解读(20)-controller和view之查找view
很多时候在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做的事情:网上有位仁兄有个很好的总结:
- 获取视图位置(GetViewLocations)
- 检查是否使用了区域(Area)
- 如果使用了区域,则把areaLocations传入
- GetViewLocations方法会将locations和areaLocations这两个字符串数组包装和合并成一个ViewLocation的集合
- 如果集合没有东西,那么抛异常
- 缓存检索
- 获取路径
- 如果名称像是一个绝对路径("/"或"~"开头)
- 检查虚拟路径所指向的文件是否存在(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的更多相关文章
- jvm源码解读--20 结合jvm源码理解 java 设计模式 模板方法
write by 张艳涛 前言: 在学习jvm之前,看过设计模式的书,知道模板方法的设计模式,今天在看java并发编程的艺术里面关于AbstractQueuedSynchronizer 用法,这个就使 ...
- DRF(1) - REST、DRF(View源码解读、APIView源码解读)
一.REST 1.什么是编程? 数据结构和算法的结合. 2.什么是REST? 首先回顾我们曾经做过的图书管理系统,我们是这样设计url的,如下: /books/ /get_all_books/ 访问所 ...
- REST、DRF(View源码解读、APIView源码解读)
一 . REST 前言 1 . 编程 : 数据结构和算法的结合 .小程序如简单的计算器,我们输入初始数据,经过计算,得到最终的数据,这个过程中,初始数据和结果数据都是数据,而计算 ...
- Restful 1 -- REST、DRF(View源码解读、APIView源码解读)及框架实现
一.REST 1.什么是编程? 数据结构和算法的结合 2.什么是REST? - url用来唯一定位资源,http请求方式来区分用户行为 首先回顾我们曾经做过的图书管理系统,我们是这样设计url的,如下 ...
- spring IOC DI AOP MVC 事务, mybatis 源码解读
demo https://gitee.com/easybao/aop.git spring DI运行时序 AbstractApplicationContext类的 refresh()方法 1: pre ...
- AFNetworking 3.0 源码解读(九)之 AFNetworkActivityIndicatorManager
让我们的APP像艺术品一样优雅,开发工程师更像是一名匠人,不仅需要精湛的技艺,而且要有一颗匠心. 前言 AFNetworkActivityIndicatorManager 是对状态栏中网络激活那个小控 ...
- AFNetworking 3.0 源码解读(三)之 AFURLRequestSerialization
这篇就讲到了跟请求相关的类了 关于AFNetworking 3.0 源码解读 的文章篇幅都会很长,因为不仅仅要把代码进行详细的的解释,还会大概讲解和代码相关的知识点. 上半篇: URI编码的知识 关于 ...
- MVC系列——MVC源码学习:打造自己的MVC框架(四:了解神奇的视图引擎)
前言:通过之前的三篇介绍,我们基本上完成了从请求发出到路由匹配.再到控制器的激活,再到Action的执行这些个过程.今天还是趁热打铁,将我们的View也来完善下,也让整个系列相对完整,博主不希望烂尾. ...
- MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)
前言:上篇介绍了下自己的MVC框架前两个版本,经过两天的整理,版本三基本已经完成,今天还是发出来供大家参考和学习.虽然微软的Routing功能已经非常强大,完全没有必要再“重复造轮子”了,但博主还是觉 ...
随机推荐
- HttpUnit学习笔记
<!-- htmlUnit --> <dependency> <groupId>net.sourceforge.htmlunit</groupId> & ...
- .NET基础之集合
集合可以说是数组的超集,集合可以维护对象数组,集合包含了更高级的功能.例如控制对其包含的对象的访问.搜索和排序等.数组是固定的,一旦我们创建好了数组,不能在现有数组的末尾添加新项,除非我们创建新的数组 ...
- cadence 焊盘制作小结
因为以前一直用altium designer 话PCB,做封装的时候焊盘是不用自己操心的,但是开始用cadence以后发现好多以前不太懂的东西,需要自己画焊盘,这就导致需要了解好多自己以前不懂的东西, ...
- 【CSLA】Component-based,Scalable,LogicalArchitecture
我能说我没看懂吗 ? http://www.cnblogs.com/lonely7345/archive/2010/02/06/1665171.html
- (转载)Cocos2dx-OpenGL ES2.0教程:你的第一个立方体(5)
在上篇文章中,我们介绍了VBO索引的使用,使用VBO索引可以有效地减少顶点个数,优化内存,提高程序效率. 本教程将带领大家一起走进3D–绘制一个立方体.其实画立方体本质上和画三角形没什么区别,所有的模 ...
- MongoDB安装,启动,注册为windows系统服务
MongoDB安装与启动 周建旭 2014-08-10 解压完后配置环境变量 下载Windows 32-bit或64-bit版本并解压缩,程序文件都在bin目录中,其它两个目录分别是C++调用是的头文 ...
- 第一个js面向对象的小实验
$.extend({ cal: function (num1,num2,oper,aftercal) { this.n1 = num1; ...
- 1206: [HNOI2005]虚拟内存 - BZOJ
Description 操作系统中一种重要的存储管理技术就是虚拟内存技术.操作系统中允许进程同时运行,也就是并行.每个进程都有其相对独立的数据块(进程运行的过程中将对其进行读写操作).理想的情况下,这 ...
- asp防注入安全问题
一.古老的绕验证漏洞虽然古老,依然存在于很多小程序之中,比如一些企业网站的后台,简单谈谈.这个漏洞出现在没有对接受的变量进行过滤,带入数据库判断查询时,造成SQL语句的逻辑问题.例如以下代码存在问题: ...
- Linq基本用法