今天这篇博文主要讲解自定义视图引擎,大家都知道在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开源网上商城第五讲:自定义视图引擎的更多相关文章

  1. BrnShop开源网上商城第一讲:架构设计

    首先在此感谢大家对BrnShop项目的支持和鼓励!我们在发布BrnShop以前曾推测项目会受到不少园友的支持,但没想到园友们的支持大大超过我们的预测.4天6000次浏览,140个推荐,170个评论,8 ...

  2. BrnShop开源网上商城第六讲:扩展视图功能

    在正式讲解扩展视图功能以前,我们有必要把视图的工作原理简单说明下.任何一个视图都会被翻译成一个c#类,并保存到指定的位置,然后被编译.这也就是为什么能在视图中包含c#代码片段的原因.下面我们通过一个项 ...

  3. BrnShop开源网上商城第四讲:自定义插件

    重要通知:BrnShop企业版NOSQL设计(基于Redis)已经开源!源码内置于最新版的BrnShop中,感兴趣的园友可以去下载来看看.官网地址:www.brnshop.com. 好了现在进入今天的 ...

  4. BrnShop开源网上商城第二讲:ASP.NET MVC框架

    在团队设计BrnShop的web项目之初,我们碰到了两个问题,第一个是数据的复用和传递,第二个是大mvc框架和小mvc框架的选择.下面我依次来说明下. 首先是数据的复用和传递:对于BrnShop的每一 ...

  5. BrnShop开源网上商城第三讲:插件的工作机制

    这几天BrnShop的开发工作比较多,所以这一篇文章来的晚了一些,还请大家见谅呀!还有通知大家一下BrnShop1.0.312版本已经发布,此版本添加了报表统计等新功能,需要源码的园友可以点此下载.好 ...

  6. (翻译)为你的MVC应用程序创建自定义视图引擎

    Creating your own MVC View Engine For MVC Application 原文链接:http://www.codeproject.com/Articles/29429 ...

  7. 自定义视图引擎,实现MVC主题快速切换

    一个网站的主题包括布局,色调,内容展示等,每种主题在某些方面应该或多或少不一样的,否则就不能称之为不同的主题了.每一个网站至少都有一个主题,我这里称之为默认主题,也就是我们平常开发设计网站时的一个固定 ...

  8. ASP.NET MVC扩展自定义视图引擎支持多模板&动态换肤skins机制

    ASP.NET mvc的razor视图引擎是一个非常好的.NET MVC框架内置的视图引擎.一般情况我们使用.NET MVC框架为我们提供的这个Razor视图引擎就足够了.但是有时我们想在我们的项目支 ...

  9. ASP.NET MVC自定义视图引擎ViewEngine 创建Model的专属视图

    MVC内置的视图引擎有WebForm view engine和Razor view engine,当然也可以自定义视图引擎ViewEngine. 本文想针对某个Model,自定义该Model的专属视图 ...

随机推荐

  1. Poj 1503 Integer Inquiry

    1.链接地址: http://poj.org/problem?id=1503 2.题目: Integer Inquiry Time Limit: 1000MS   Memory Limit: 1000 ...

  2. Poj/OpenJudge 1094 Sorting It All Out

    1.链接地址: http://poj.org/problem?id=1094 http://bailian.openjudge.cn/practice/1094 2.题目: Sorting It Al ...

  3. TweenMax动画库学习(二)

    目录            TweenMax动画库学习(一)            TweenMax动画库学习(二)            TweenMax动画库学习(三)            Tw ...

  4. 关于Angular.js Routing 的学习笔记(实现单页应用)

    最近开始学习angular.js,发现angular.js确实很方便,也很强大.在看到 AngularJS Routing and Multiple Views 这一部分的时候,有点乱.现在通过记录一 ...

  5. SAP Java Connector(JCo)

    JCo是一个高性能的,基于JNI的中间件,它实现了SAP的RFC(Remote Function Call)协议. 1.JCo的安装 从 http://files.cnblogs.com/byfhd/ ...

  6. MIT 2012分布式课程基础源码解析-线程池实现

    主要内容 ScopedLock 队列实现 线程池实现 在正式讲解线程池实现之前,先讲解两个有用的工具类: ScopedLock fifo队列 ScopedLock: ScopedLock是局域锁的实现 ...

  7. About building ant & install ant on centos7 {ant source code 1.94}

                  hamcrest-junit-2.0.0.0.jar java-hamcrest-2.0.0.0.jar   copy to ant-sourceCodeDir/lib/o ...

  8. Python学习笔记—itertools模块

    这篇是看wklken的<Python进阶-Itertools模块小结> 学习itertools模块的学习笔记 在看itertools中各函数的源代码时,刚开始还比较轻松,但后面看起来就比较 ...

  9. post请求json内容丢失问题

    今天在项目组用json传输数据 post方法提交 发现传输过去的数据json内的+ 号被直接干掉了. 后来传输之前直接先编码. 接收端: public void ProcessRequest(Http ...

  10. [C和指针] rearrange.c

    C和指针_程序1.1_重排字符 /* ** 这个程序从标准输入(键盘)中读取输入行并按需求处理后在标准输出(屏幕)中打印, ** 每个输入行的后面一行是该行按需求处理后的输出内容. ** ** 输入的 ...