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功能已经非常强大,完全没有必要再“重复造轮子”了,但博主还是觉 ...
随机推荐
- DICOM开源库
转载于 http://blog.csdn.net/jackmacro/article/details/5850142 Developers used to search for libraries , ...
- NVIDIA显卡设置提升MineCraft流畅度
很喜欢MineCraft(我的世界)这款游戏.可09年Y450的配置现在看来有点弱,尽管将Y450的CPU升级至了2.8Ghz的T9600,内存升级至1066Mhz的4G双通道内存,硬盘更换为128G ...
- iOS本地动态验证码生成-b
用于ios本地动态生成验证码,效果如下: demo.gif 导入CoreGraphics.framework用于绘制图形 封装UIView,便捷使用,代码如下: AuthcodeView.h #imp ...
- 在工程中添加pch文件
在Xcode6之前,新建一个工程的时候,系统会帮我们自动新建一个以工程名为名字的pch (precompile header)文件,在开发过程中,可以将那些整个工程都广泛使用的头文件包含在该文件下,编 ...
- 1064: [Noi2008]假面舞会 - BZOJ
Description 一年一度的假面舞会又开始了,栋栋也兴致勃勃的参加了今年的舞会.今年的面具都是主办方特别定制的.每个参加舞会的人都可以在入场时选择一 个自己喜欢的面具.每个面具都有一个编号,主办 ...
- 团体程序设计天梯赛-练习集L1-007. 念数字
L1-007. 念数字 时间限制 400 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 翁恺 输入一个整数,输出每个数字对应的拼音.当整数为负数时,先 ...
- Android Activity交互及App交互
Android交互--------->Intent Activity之间----->Explicit Intent App之间--------->Implicit Intent
- FireFly 服务端 Unity3D黑暗世界 客户端 问题
启动服务端成功截图: 连接成功截图: 测试服务端是否启动成功: 在Web输入:http://localhost:11009/ 按回车 (查看cmd启动的服务端 是否多出如下显示) 服务端启动成功.P ...
- ie6 iframe src="javascript:" 报安全警报问题
<iframe id="shuaka_iframe" class="embed-page-iframe" data-src="https://w ...
- 如何在eclipse里使用git
新版都自带git插件了.在项目上右键,选team,选share project,再选择git就可以了. 如果在本地使用git比较简单.如果要多人共享的使用git,那么需要专门的服务器,并提供ssh,这 ...