BrnShop开源网上商城第五讲:自定义视图引擎
今天这篇博文主要讲解自定义视图引擎,大家都知道在asp.net mvc框架中默认自带一个Razor视图引擎,除此之外我们也可以自定义自己的视图引擎,只需要实现IViewEngine接口,接口定义如下:
- ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
- ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
- void ReleaseView(ControllerContext controllerContext, IView view)
下面我们详细介绍下这三个方法,首先是FindView方法:这个方法的作用是使用指定的控制器上下文来查找指定的视图,说的通俗点就是当我们在控制器中使用诸如"return View("视图名称")"方法时来查找指定的视图文件时使用的。它的四个参数解释如下:
- controllerContext:控制器上下文。这个大家都知道什么意思所以就不说了
- viewName:视图名称。在控制器中我们一般这样指定:return View("视图名称")
- masterName:母版页视图名称。也就是我们在视图文件中通过"Layout"指定的视图名称
- useCache:是否使用缓存。如果使用缓存就自动在缓存记录中查找,此时如果找到就直接使用,如果没有找到再去磁盘上查找。如果不使用缓存需要每次都到磁盘本地进行查找
再来说下FindPartialView方法:这个方法的作用是使用指定的控制器上下文查找指定的分部视图,和上面方法对比就知道这个方法是用来查找分部视图的。我们在控制器中使用诸如"return PartialView("分部视图名称")"方法时来查找指定的分部视图文件时使用的。它的三个参数(由于分部视图没有母版页所以不需要masterName参数)解释如下:
- controllerContext:控制器上下文。
- partialViewName:分部视图名称。在控制器中我们一般这样指定:return PartialView("分部视图名称")
- useCache:是否使用缓存。具体解释参见FindView方法
最后是ReleaseView方法,这个方法的作用是使用指定的控制器上下文来释放指定的视图,说白了就是释放视图使用的资源。它的参数比较简单,具体如下:
- controllerContext:控制器上下文。
- view:视图。我们知道所有的视图文件在运行时都会编译成一个类,这个类实现了IView 接口,所以这个参数就是指的这个类。
如果我们想自定义自己的视图引擎只需要实现自定义一个类,然后此类继承IViewEngine接口并实现它的3个方法就可以了。但这样做我们的工作量会很大,所以有没有一种更方便的方法呢?答案是有的,那就是继承VirtualPathProviderViewEngine类,然后重写FindView和FindPartialView方法就可以了(ReleaseView方法不要重写,可以直接使用VirtualPathProviderViewEngine中的实现)。现在我们来看看VirtualPathProviderViewEngine到底是何方神物,代码如下:
|
1
|
abstract class VirtualPathProviderViewEngine : IViewEngine |
由于VirtualPathProviderViewEngine类的代码太多,所以我只帖出了它的定义,大家可以看到VirtualPathProviderViewEngine类已经实现了IViewEngine接口,所以我们可以借助这个类来简化我们自定义视图引擎的复杂度和工作量。
下面我们以多店版网上商城BrnMall为例带着大家做一个例子:
首先我们定义一个类ThemeVirtualPathProviderViewEngine,并继承自VirtualPathProviderViewEngine,代码如下:
|
1
2
3
4
|
/// <summary>/// 主题路径提供者视图引擎/// </summary>public abstract class ThemeVirtualPathProviderViewEngine : VirtualPathProviderViewEngine |
继承了这个类后我们就可以重写它的FindView和FindPartialView方法了,代码如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
#region 重写方法/// <summary>/// 使用指定的控制器上下文和母版视图名称来查找指定的视图/// </summary>/// <param name="controllerContext">控制器上下文</param>/// <param name="viewName">视图的名称</param>/// <param name="masterName">母版视图的名称</param>/// <param name="useCache">若为 true,则使用缓存的视图</param>/// <returns>页视图</returns>public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache){ //判断是否为移动访问 bool mobile = WebHelper.IsMobile(); //如果为移动访问,则构建一个新的视图名称 string overrideViewName = mobile ? string.Format("{0}.{1}", viewName, _mobileviewmodifier) : viewName; //构建一个视图引擎结果 ViewEngineResult result = FindThemeView(controllerContext, overrideViewName, masterName, useCache, mobile); //如果为移动访问且没有对应视图文件时采用原视图名称解析 if (mobile && (result == null || result.View == null)) result = FindThemeView(controllerContext, viewName, masterName, useCache, false); return result;}/// <summary>/// 寻找分部视图的方法/// </summary>/// <param name="controllerContext">控制器上下文</param>/// <param name="partialViewName">分部视图的名称</param>/// <param name="useCache">若为 true,则使用缓存的分部视图</param>/// <returns>分部视图</returns>public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache){ //判断是否为移动访问 bool mobile = WebHelper.IsMobile(); //如果为移动访问,则构建一个新的分部视图名称 string overrideViewName = mobile ? string.Format("{0}.{1}", partialViewName, _mobileviewmodifier) : partialViewName; //构建一个分部视图引擎结果 ViewEngineResult result = FindThemePartialView(controllerContext, overrideViewName, useCache, mobile); //如果为移动访问且没有对应分部视图文件时采用原分部视图名称解析 if (mobile && (result == null || result.View == null)) result = FindThemePartialView(controllerContext, partialViewName, useCache, false); return result;}#endregion |
在这两个方法中我们可以根据自己的需要添加必要的东西。例如BrnMall商城需要判断访问者是否使用手机浏览器访问商城,如果是则切换到专门针对手机浏览器优化的视图(即在视图名称后面添加.Mobile,构建一个新的视图名称)。具体代码实现大家可以参考上面。
现在我们已经将我们的业务需要融合进视图引擎中,接下来就是去磁盘查找对应视图文件,不过在查找之前我们还需要做点事情,那就是构建磁盘查找路径结果,方便在视图文件没有找到时给出提示,所以我们在上面两个重写方法中并没有马上去磁盘查找视图文件,而是调用了构建查找路径结果的两个方法,具体如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
/// <summary>/// 构建视图引擎结果/// </summary>private ViewEngineResult FindThemeView(ControllerContext controllerContext, string viewName, string masterName, bool useCache, bool mobile){ //视图文件路径搜索列表 string[] strArray1 = null; //布局文件路径搜索列表 string[] strArray2 = null; //获取控制器名称 string controllerName = controllerContext.RouteData.GetRequiredString("controller"); //获取视图文件路径 string viewPath = GetPath(controllerContext, viewName, controllerName, "View", useCache, mobile, out strArray1); //当视图文件存在时 if (!string.IsNullOrWhiteSpace(viewPath)) { if (string.IsNullOrWhiteSpace(masterName)) { return new ViewEngineResult(CreateView(controllerContext, viewPath, string.Empty), this); } else { //获取布局文件的路径 string masterPath = GetPath(controllerContext, masterName, controllerName, "Master", useCache, mobile, out strArray2); if (!string.IsNullOrWhiteSpace(masterPath)) { return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this); } } } //当视图文件或布局文件不存在时将搜索路径返回 if (strArray2 == null) { return new ViewEngineResult(strArray1); } else { return new ViewEngineResult(strArray1.Union<string>(strArray2)); }}/// <summary>/// 构建分部视图引擎结果/// </summary>private ViewEngineResult FindThemePartialView(ControllerContext controllerContext, string partialViewName, bool useCache, bool mobile){ //分部视图文件路径搜索列表 string[] strArray; //获取控制器名称 string controllerName = controllerContext.RouteData.GetRequiredString("controller"); //获取分部视图文件路径 string partialViewPath = GetPath(controllerContext, partialViewName, controllerName, "Partial", useCache, mobile, out strArray); //当分部视图文件存在时 if (!string.IsNullOrWhiteSpace(partialViewPath)) { return new ViewEngineResult(CreatePartialView(controllerContext, partialViewPath), this); } //分部视图文件不存在时返回搜索路径 return new ViewEngineResult(strArray);} |
在上面的方法中我们根据GetPath方法返回的结果分别进行处理,具体如下:
- 如果返回的结果为空,代表视图文件不存在,我们需要将视图文件查找路径构建成一个数组并调用ViewEngineResult类的public ViewEngineResult(IEnumerable<string> searchedLocations)构造函数
- 如果返回的结果不为空,代表视图文件存在,那么我们调用ViewEngineResult类的public ViewEngineResult(IView view, IViewEngine viewEngine)构造函数。
关于上面有两点需要补充下:
- ViewEngineResult类:这个类代表一个视图引擎查找结果,它有两个属性分别是IEnumerable<string>类型的SearchedLocations,和IView类型的View。当我们使用构造函数ViewEngineResult(IEnumerable<string> searchedLocations)初始化时会将参数searchedLocations赋值给属性SearchedLocations,此时属性View为空。当我们使用构造函数ViewEngineResult(IView view, IViewEngine viewEngine)初始化时会将参数view赋值给属性View,此时属性SearchedLocations为空。MVC框架会根据属性View是否为空来呈现不同的结果,如果View属性为空则直接将视图路径查找结果输出,如果不为空则输出此视图。
- CreateView和CreatePartialView方法:这两个方法可以把视图文件路径转换为对应的视图(即实现IView接口的类)
万事俱备,现在可以查找视图文件的路径了,我们以多店版网上商城BrnMall为例,具体代码如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
|
/// <summary>/// 获取文件的路径/// </summary>private string GetPath(ControllerContext controllerContext, string name, string controllerName, string cacheKeyPrefix, bool useCache, bool mobile, out string[] searchedLocations){ searchedLocations = null; //视图位置列表 string[] locations = null; //主题 string theme = string.Empty; //获取区域 string area = GetRouteDataTokenValue("area", controllerContext.RouteData.DataTokens).ToLower(); if (string.IsNullOrWhiteSpace(area))//商城前台视图位置的处理 { theme = GetRouteDataTokenValue("theme", controllerContext.RouteData.DataTokens); if (theme == "")//商城页面 { locations = new string[2] { "~/Views/{1}/{0}.cshtml", "~/Views/Shared/{0}.cshtml" }; } else//店铺页面 { locations = new string[1] { "~/Themes/{2}/Views/{0}.cshtml" }; } } else//店铺后台和商城后台视图位置的处理 { //不能通过移动访问后台 if (mobile) { searchedLocations = new string[0]; return string.Empty; } if (area == "storeadmin")//访问店铺后台管理区域 { locations = new string[2] { "~/Admin_Store/Views/{1}/{0}.cshtml", "~/Admin_Store/Views/Shared/{0}.cshtml" }; } else if (area == "malladmin")//访问商城后台管理区域 { locations = new string[2] { "~/Admin_Mall/Views/{1}/{0}.cshtml", "~/Admin_Mall/Views/Shared/{0}.cshtml" }; } } //是否为特殊路径的标识 bool flag2 = IsSpecificName(name); //从缓存中获取视图位置 string cacheKey = CreateCacheKey(cacheKeyPrefix, name, flag2 ? string.Empty : controllerName, area, theme);//视图位置的缓存键 if (useCache) { //从缓存中得到视图位置 var cachedPath = ViewLocationCache.GetViewLocation(controllerContext.HttpContext, cacheKey); if (cachedPath != null) { return cachedPath; } } //如果视图位置不在缓存中,则构建视图位置并存储到缓存中 if (!flag2)//不是特殊路径时的操作 { return GetPathFromGeneralName(controllerContext, locations, name, controllerName, theme, cacheKey, ref searchedLocations); } else//特殊路径时的操作 { return GetPathFromSpecificName(controllerContext, name, cacheKey, ref searchedLocations); }} |
在这个方法中我们根据当前访问的位置(商城页面,店铺页面,店铺后台页面,系统后台页面)构建不同的查找路径,具体大家可以参考商品的if else 语句。
这里有两点需要注意下,第一点是视图文件名的格式问题:
我们都知道在控制器的View方法中我们可以传入视图文件名称,也可以传入视图文件的路径,代码如下:
|
1
2
|
return View("视图文件名称");//只传入视图文件名称return View("~/Views/视图文件名称.cshtml");//传入视图文件的路径 |
所以我们需要判断此时视图名称的类型,具体代码如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
|
/// <summary>/// 判读视图名称是否以“~”或“/”开头/// </summary>private bool IsSpecificName(string name){ char ch = name[0]; if (ch != '~') { return (ch == '/'); } return true;} |
第二点是视图路径缓存问题,当参数useCache为真时我们首先通过缓存获取路径,如果缓存中存在则直接返回,否则去磁盘上查找。缓存键的生成代码如下:
|
1
2
3
4
5
6
7
|
/// <summary>/// 创建视图位置的缓存键/// </summary>private string CreateCacheKey(string prefix, string name, string controllerName, string area, string theme){ return string.Format(":ViewCacheKey:{0}:{1}:{2}:{3}:{4}:{5}", new object[] { base.GetType().AssemblyQualifiedName, prefix, name, controllerName, area, theme });} |
最后就是真正的视图文件路径查找了,具体代码如下
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
/// <summary>/// 特殊名称时构建视图路径/// </summary>private string GetPathFromSpecificName(ControllerContext controllerContext, string name, string cacheKey, ref string[] searchedLocations){ //将路径添加到视图位置缓存 ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, name); //扩展名不为“.cshtml”或者文件不存在时 if (!IsSupportedExtension(name) || !FileExists(controllerContext, name)) { searchedLocations = new string[] { name }; return string.Empty; } return name;}/// <summary>/// 普通名称时构建视图路径/// </summary>private string GetPathFromGeneralName(ControllerContext controllerContext, string[] viewLocationFormats, string name, string controllerName, string theme, string cacheKey, ref string[] searchedLocations){ int count = viewLocationFormats.Length; searchedLocations = new string[count]; //循环视图位置 for (int i = 0; i < count; i++) { string path = string.Format(viewLocationFormats[i], name, controllerName, theme); if (FileExists(controllerContext, path)) { searchedLocations = null; ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, path); return path; } //将路径添加到搜索位置列表中 searchedLocations[i] = path; } return string.Empty;} |
如果视图名称为视图路径格式时直接查找路径指定的文件是否存在;如果视图名称为普通名称时根据路径格式列表依次构件真实路径并查找。无论上述哪种情况,当视图文件不存在时都要将查找路径输出,视图文件存在且使用缓存时将其保存到缓存中以便下次直接使用。
到了这步还不算完,因为这只是自定义了路径查找类,还需要两步来构造完整的视图引擎。第一步实现主题构建引擎,代码如下:
|
1
2
3
4
5
6
7
8
9
10
11
|
/// <summary>/// 主题构建引擎/// </summary>public abstract class ThemeBuildManagerViewEngine : ThemeVirtualPathProviderViewEngine{ //判读文件是否存在 protected override bool FileExists(ControllerContext controllerContext, string virtualPath) { return BuildManager.GetObjectFactory(virtualPath, false) != null; }} |
第二步是实现视图引擎,代码如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
/// <summary>/// 主题视图引擎/// </summary>public class ThemeRazorViewEngine : ThemeBuildManagerViewEngine{ /// <summary> /// 创建Razor视图 /// </summary> protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath) { return new RazorView(controllerContext, viewPath, masterPath, true, FileExtensions); } /// <summary> /// 创建Razor分部视图 /// </summary> protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath) { return new RazorView(controllerContext, partialPath, null, false, FileExtensions); }} |
至此我们的自定义视图引擎已经完成,如果想要使用此引擎只需要在Global.asax中替换掉默认视图引擎即可。代码如下:
|
1
2
3
4
5
6
7
|
protected void Application_Start(){ //将默认视图引擎替换为ThemeRazorViewEngine引擎 ViewEngines.Engines.Clear(); ViewEngines.Engines.Add(new ThemeRazorViewEngine());} |
有对网上商城程序设计感兴趣的朋友,欢迎加入QQ群:235274151,大家可以交流下!
BrnShop开源网上商城第五讲:自定义视图引擎的更多相关文章
- BrnShop开源网上商城第一讲:架构设计
首先在此感谢大家对BrnShop项目的支持和鼓励!我们在发布BrnShop以前曾推测项目会受到不少园友的支持,但没想到园友们的支持大大超过我们的预测.4天6000次浏览,140个推荐,170个评论,8 ...
- BrnShop开源网上商城第六讲:扩展视图功能
在正式讲解扩展视图功能以前,我们有必要把视图的工作原理简单说明下.任何一个视图都会被翻译成一个c#类,并保存到指定的位置,然后被编译.这也就是为什么能在视图中包含c#代码片段的原因.下面我们通过一个项 ...
- BrnShop开源网上商城第四讲:自定义插件
重要通知:BrnShop企业版NOSQL设计(基于Redis)已经开源!源码内置于最新版的BrnShop中,感兴趣的园友可以去下载来看看.官网地址:www.brnshop.com. 好了现在进入今天的 ...
- BrnShop开源网上商城第二讲:ASP.NET MVC框架
在团队设计BrnShop的web项目之初,我们碰到了两个问题,第一个是数据的复用和传递,第二个是大mvc框架和小mvc框架的选择.下面我依次来说明下. 首先是数据的复用和传递:对于BrnShop的每一 ...
- BrnShop开源网上商城第三讲:插件的工作机制
这几天BrnShop的开发工作比较多,所以这一篇文章来的晚了一些,还请大家见谅呀!还有通知大家一下BrnShop1.0.312版本已经发布,此版本添加了报表统计等新功能,需要源码的园友可以点此下载.好 ...
- (翻译)为你的MVC应用程序创建自定义视图引擎
Creating your own MVC View Engine For MVC Application 原文链接:http://www.codeproject.com/Articles/29429 ...
- 自定义视图引擎,实现MVC主题快速切换
一个网站的主题包括布局,色调,内容展示等,每种主题在某些方面应该或多或少不一样的,否则就不能称之为不同的主题了.每一个网站至少都有一个主题,我这里称之为默认主题,也就是我们平常开发设计网站时的一个固定 ...
- ASP.NET MVC扩展自定义视图引擎支持多模板&动态换肤skins机制
ASP.NET mvc的razor视图引擎是一个非常好的.NET MVC框架内置的视图引擎.一般情况我们使用.NET MVC框架为我们提供的这个Razor视图引擎就足够了.但是有时我们想在我们的项目支 ...
- ASP.NET MVC自定义视图引擎ViewEngine 创建Model的专属视图
MVC内置的视图引擎有WebForm view engine和Razor view engine,当然也可以自定义视图引擎ViewEngine. 本文想针对某个Model,自定义该Model的专属视图 ...
随机推荐
- Head First 设计模式系列之二----备忘录模式(java版)
申明:这几天无意中关注到备忘录模式,比较陌生回家一番参考书,只在附录里记录了该模式.后来在园子里有发现了有专门写设计模式的博客,并且写的也得牛逼.附上链接 http://www.cnblogs.com ...
- Linux FTP服务安装和远程登录失败
问题:本机VPlayer安装pure-ftpd ftp服务,通过flashfxp从windows连接出现以下错误: [左] 正在连接到 vmare -> IP=192.168.174.133 ...
- Web前端新人之CSS样式选择器
最近在学习css样式.那么我就想先整理一下css样式的选择器 规则结构: 每个规则都有两个基本部分:选择器和声明块.声明块由一个或者多个声明组成,每个声明则是一个属性—值对(property-valu ...
- /dev/null 2>&1 解释(转)
cmd >a 2>a 和 cmd >a 2>&1 为什么不同? cmd >a 2>a :stdout和stderr都直接送往文件 a ,a文件会被打开两遍, ...
- MVC之重定向
MVC的重定向主要通过RedirectResult和RedirectToRouteResult实现.很显然,这两个对象都是MVC返回对象ActionResult的两个继承,具体原理不赘述. 这两个方法 ...
- RAC 安装完成后 节点间通信不依赖于SSH
RAC 安装完成后,想修改ssh 的端口.google了一下.原文https://community.oracle.com/thread/2444594?tstart=0 原文说的是11g,10g也好 ...
- 021,lambda 表达式
021,lambda 表达式 匿名函数: 快速定义单行的最小函数,是从lisp借用来的,可以用在任何需要函数的地方 >>> def ds(x): return 2*x + ...
- python常用绘图软件包记录
在没有使用python之前,觉得matlab的绘图功能还算可以~但现在发现python的绘图包真的好强大,绘制出的图像非常专业漂亮,但具体使用还有待学习,这里记录学习过程中遇到的python绘图包,以 ...
- delphi xe5 android 使用样式(风格)
1.在界面上添加 TStyleBook 控件 2.点击Resource 选择xe5程序安装带的几个风格的其中之一,路径存放在: C:\Program Files\Embarcadero\RAD Stu ...
- 对C语言中sizeof细节的三点分析
转自对C语言中sizeof细节的三点分析 1.sizeof是运算符,跟加减乘除的性质其实是一样的,在编译的时候进行执行,而不是在运行时才执行. 那么如果编程中验证这一点呢?ps:这是前两天朋友淘宝面试 ...