一个网站的主题包括布局,色调,内容展示等,每种主题在某些方面应该或多或少不一样的,否则就不能称之为不同的主题了。每一个网站至少都有一个主题,我这里称之为默认主题,也就是我们平常开发设计网站时的一个固定布局,固定色调,固定内容展示等构成一个默认的固定主题。单一主题针对一些小网站或网站用户群体相对单一固定还是比较适用的,但如果是大型网站或是网站的用户群体比较多而且复杂,如:京东博客园里的每个博客空间、文俊IT社区网(我的网站,虽不是大型网站,但也实现了主题切换功能的哦!~)等,是需要多个网站主题的,当然我举的这两个网站他们都实现了多主题,比如:

京东默认主题:适合国内人群

英文主题:适合国外人群

博客园就不用在此举例了吧,大家看每个人的博客风格不同就知道了。

上面的表述其作用是为了说明主题对于一个大中型或多种不同用户群体的网站的重要性,而如何实现多种主题的实现与切换也是本文主要说明的。主题的实现与切换方法有很多,常见的方法有:动态替换CSS+JS、ASPX页面可以采取制作多种控件主题进行切换、切换页面路径,不同的路径同页面文件设计成不同的主题等,而我这里要讲解的是MVC下如何实现通过切换路径来实现主题的切换功能。

MVC页面展示的内容来自视图页面(view.cshtml,view.vbhtml),如果说要实现在相同的后台处理代码下,实现不同的主题展示,我们只需要改变视图页面内容就可以了,但默认情况下,一个Action对应一个视图,如何实现一个Action在某种主题下对应不同的的视图文件呢?我这里给出如下两种方案:

第一种方案,很简单,可以根据请求的主题名称来返回不同的视图,代码实现如下:

Action:

        public ActionResult TestCustomViewPath(string theme)
{
string themeName = string.IsNullOrEmpty(theme) ? "Default" : theme;
return View(themeName + "/Test"); //注意这里指定了一相对路径
}

View:

//Default:

<!DOCTYPE html>

<html>
<head>
<title></title>
</head>
<body>
<div>
我是自定义的视图路径,风格:@ViewBag.CurrentThemeName
</div>
@using(Html.BeginForm()){
<span>选择主题:</span> @Html.DropDownList("theme", new[] { new SelectListItem() { Text = "默认", Value = "Default" },
new SelectListItem() { Text = "绿色", Value = "Green" }})
<p>
<input type="submit" value="应用" />
</p>
}
</body>
</html> //Green: <!DOCTYPE html> <html>
<head>
<title></title>
</head>
<body style="background-color:Green;color:Orange;">
<div>
我是自定义的视图路径,风格:@ViewBag.CurrentThemeName
</div>
@using(Html.BeginForm()){
<span>选择主题:</span> @Html.DropDownList("theme", new[] { new SelectListItem() { Text = "默认", Value = "Default" },
new SelectListItem() { Text = "绿色", Value = "Green" }})
<p>
<input type="submit" value="应用" />
</p>
}</body>
</html>

注意这里面的视图文件,不再是之前的默认情况,而是如下结构:即:在同一个控制器对应的视图目录下存在多个以主题为名的子目录,子目录中的文件名相同。

 

实现效果如下图示:

从上面的效果来看,确实已经实现了所谓的主题切换,但这种方法我并不是很推荐,因为视图文件目录过于混乱,每个控制器视图目录下均包含多个主题子目录,且每个子目录下的文件名相同,这让我很难区别,容易出现更错的情况,有人可能会说,我也可以将视图文件名改成不同的呀,比如:Green_Test.cshtml,这样就能区分了,但我仍然说不建议这样做,至少找视图文件都找得麻烦,推荐我下面介绍的第2种方法,具体如何实现,请君往下看。

我们知道,视图如何呈现,或者说如何被解析,均是需要视图引擎ViewEngine来支持的,微软给我们提供了两种视图引擎,即:WebFormViewEngine,对应的视图文件扩展名为:aspx;RazorViewEngine,对应的视图文件扩展名为:cshtml,vbhtml;从如下源代码也可以看出来:

namespace System.Web.Mvc
{
public static class ViewEngines
{
private static readonly ViewEngineCollection _engines = new ViewEngineCollection
{
new WebFormViewEngine(),
new RazorViewEngine()
}; public static ViewEngineCollection Engines
{
get
{
return ViewEngines._engines;
}
}
}
}

看到这个代码,我们应该可以看出,可以通过ViewEngines.Engines来添加我们自定义或是第三方的视图引擎(如:Nvelocity),来实现支持不同的视图文件格式,我这里因为目的是需要实现主题切换,所以就来自定义视图引擎,因为视图引擎实现细节比较复杂,若直接继承自IViewEngine接口,需要考虑与实现的代码会比较多且存在风险,所以我是直接继承自RazorViewEngine,既然要实现该类,我们先看一下该类的源代码:

using System;
using System.Collections.Generic; namespace System.Web.Mvc
{
public class RazorViewEngine : BuildManagerViewEngine
{
internal static readonly string ViewStartFileName = "_ViewStart"; public RazorViewEngine() : this(null)
{
} public RazorViewEngine(IViewPageActivator viewPageActivator) : base(viewPageActivator)
{
base.AreaViewLocationFormats = new string[]
{
"~/Areas/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{2}/Views/{1}/{0}.vbhtml",
"~/Areas/{2}/Views/Shared/{0}.cshtml",
"~/Areas/{2}/Views/Shared/{0}.vbhtml"
};
base.AreaMasterLocationFormats = new string[]
{
"~/Areas/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{2}/Views/{1}/{0}.vbhtml",
"~/Areas/{2}/Views/Shared/{0}.cshtml",
"~/Areas/{2}/Views/Shared/{0}.vbhtml"
};
base.AreaPartialViewLocationFormats = new string[]
{
"~/Areas/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{2}/Views/{1}/{0}.vbhtml",
"~/Areas/{2}/Views/Shared/{0}.cshtml",
"~/Areas/{2}/Views/Shared/{0}.vbhtml"
};
base.ViewLocationFormats = new string[]
{
"~/Views/{1}/{0}.cshtml",
"~/Views/{1}/{0}.vbhtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/{0}.vbhtml"
};
base.MasterLocationFormats = new string[]
{
"~/Views/{1}/{0}.cshtml",
"~/Views/{1}/{0}.vbhtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/{0}.vbhtml"
};
base.PartialViewLocationFormats = new string[]
{
"~/Views/{1}/{0}.cshtml",
"~/Views/{1}/{0}.vbhtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/{0}.vbhtml"
};
base.FileExtensions = new string[]
{
"cshtml",
"vbhtml"
};
} protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
{
string layoutPath = null;
bool runViewStartPages = false;
IEnumerable<string> fileExtensions = base.FileExtensions;
IViewPageActivator viewPageActivator = base.ViewPageActivator;
return new RazorView(controllerContext, partialPath, layoutPath, runViewStartPages, fileExtensions, viewPageActivator);
} protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
bool runViewStartPages = true;
IEnumerable<string> fileExtensions = base.FileExtensions;
IViewPageActivator viewPageActivator = base.ViewPageActivator;
return new RazorView(controllerContext, viewPath, masterPath, runViewStartPages, fileExtensions, viewPageActivator);
}
}
}

有没有发现有什么值得我们关注的地方呢?对了,就是构造函数,构造函数中直接初始化了7个数组,除了FileExtensions外,其余6个均是用来存放视图查找的路径的,中间的{0}\{1}\{2}分别为view,controller,area的占位符,在执行查找视图时会被替换成实际的值。知道了这6个数组,等于就掌握了视图查找路径,我们只需要重新赋值这个6个数组,就能实现视图的随意切换了,以下面我实现的自定义视图引擎代码:

    public class CustomViewEngine : RazorViewEngine
{
public CustomViewEngine(string theme)
{
FormatLocations(base.AreaViewLocationFormats, theme);
FormatLocations(base.AreaMasterLocationFormats, theme);
FormatLocations(base.AreaPartialViewLocationFormats, theme);
FormatLocations(base.ViewLocationFormats, theme);
FormatLocations(base.MasterLocationFormats, theme);
FormatLocations(base.PartialViewLocationFormats, theme);
FormatLocations(base.MasterLocationFormats, theme);
} private void FormatLocations(string[] locationFormats,string theme)
{
for (int i = 0; i < locationFormats.Length; i++)
{
locationFormats[i] = locationFormats[i].Replace("/Views/",string.Format("/Views/{0}/",theme));
}
}
}

这个类很简单,只有一个构造函数及一个私有方法FormatLocations,目的就是改变默认的视图查找路径。

有了这个视图引擎,要实现主题切换就很简单了,实现方式如下:

1.全局实现:

public class HomeController : Controller
{ private string themeName = null; protected override void OnResultExecuting(ResultExecutingContext filterContext)
{
var viewResult = filterContext.Result as ViewResult;
if (viewResult != null)
{
string themeCookieName="ThemeName";
if (!string.IsNullOrEmpty(themeName))
{
HttpContext.Response.Cookies.Set(new HttpCookie(themeCookieName, themeName) { Expires=DateTime.Now.AddYears(1)});
}
else
{
themeName = (HttpContext.Request.Cookies[themeCookieName].Value??"").Trim();
}
ViewEngineCollection viewEngines = new ViewEngineCollection() { new CustomViewEngine(themeName) };
foreach (var item in ViewEngines.Engines)
{
viewEngines.Add(item);
} viewResult.ViewEngineCollection = viewEngines;
//viewResult.ViewEngineCollection = new ViewEngineCollection(new List<IViewEngine> { new CustomViewEngine(themeName) });//这里是只用自定义视图引擎
}
base.OnResultExecuting(filterContext);
} public ActionResult TestCustomViewEngine(string theme)
{
themeName = string.IsNullOrEmpty(theme) ? "Default" : theme;
ViewBag.CurrentThemeName = themeName;
var viewResult = View("Test");
return viewResult;
} }

注意上面代码中定义的私有字段themeName,这个主要是用作第一次设置主题时会用到,因为如果单纯的靠Cookie是不行的,Cookie在没有输出到客户端时,Request.Cookies是空的,那样的话就会存在延迟,不信的话,大家可以试试,另一个地方是viewResult.ViewEngineCollection,这里我是采用将自定义与默认的引擎都放到引擎集合中,这样可以保证兼容多种路径下视图,当然如果只希望应用自定义的引擎,则可以初始化一个引擎集合且里面只有自定义的视图引擎。

这里也可以将这些方法定义到一个ThemeController中,然后其它的控制器继承自它就可以了,还可以实现过滤器,然后再应用到ACTION上。

2.单个视图实现:仅作演示,一般不会这么用

        public ActionResult TestCustomViewEngine(string theme)
{
string themeName = string.IsNullOrEmpty(theme) ? "Default" : theme;
var viewResult = View("Test");
viewResult.ViewEngineCollection = new ViewEngineCollection(new List<IViewEngine> { new CustomViewEngine(themeName) });
ViewBag.CurrentThemeName = themeName;
return viewResult;
}

  

视图内容:

Views/Default/Home/Test.cshtml

<!DOCTYPE html>

<html>
<head>
<title></title>
</head>
<body>
<div>
我是自定义的视图路径,风格:@ViewBag.CurrentThemeName
</div>
@using(Html.BeginForm()){
<span>选择主题:</span> @Html.DropDownList("theme", new[] { new SelectListItem() { Text = "默认", Value = "Default" },
new SelectListItem() { Text = "绿色", Value = "Green" }})
<p>
<input type="submit" value="应用" />
</p>
}
</body>
</html>

Views/SkyBlue/Home/Test.cshtml

<!DOCTYPE html>

<html>
<head>
<title></title>
</head>
<body style="background-color:skyblue;color:Green;">
<div>
我是自定义的视图路径,风格:@ViewBag.CurrentThemeName
</div>
@using(Html.BeginForm()){
<span>选择主题:</span> @Html.DropDownList("theme", new[] { new SelectListItem() { Text = "默认", Value = "Default" },
new SelectListItem() { Text = "天空蓝", Value = "SkyBlue" }})
<p>
<input type="submit" value="应用" />
</p>
}
</body>
</html>

视图文件目录结构如下图示:

最终的实现主题切换效果如下图所示:

当切到其它视图,如:View/Home/Index

主题切换实现成功,到此结束,欢迎大家讨论,写代码很快,但写文章却实有点耗时间,花了我几个小时,主要是需要将设计思路讲出来,好让大家明白,我不想写那种只讲结果不讲原理及设计思路的文章,想让大家看了我的文章,不仅会用,还能明白实现原理,最后大家都能自己设计出更好的方案。独乐乐不如众乐乐,如果觉得这篇文章我(梦在旅途)用心了,还请支持一下哦,谢谢!

(今天是我的生日,在此祝我自己生日快乐的同时,也感叹时间过得很快啊!)

自定义视图引擎,实现MVC主题快速切换的更多相关文章

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

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

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

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

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

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

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

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

  5. BrnShop开源网上商城第五讲:自定义视图引擎

    今天这篇博文主要讲解自定义视图引擎,大家都知道在asp.net mvc框架中默认自带一个Razor视图引擎,除此之外我们也可以自定义自己的视图引擎,只需要实现IViewEngine接口,接口定义如下: ...

  6. MVC自定义视图引擎地址

    先看结构 1.RouteConfig 文件(注意顺序) public static void RegisterRoutes(RouteCollection routes) { routes.Ignor ...

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

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

  8. MVC系列——MVC源码学习:打造自己的MVC框架(四:了解神奇的视图引擎)

    前言:通过之前的三篇介绍,我们基本上完成了从请求发出到路由匹配.再到控制器的激活,再到Action的执行这些个过程.今天还是趁热打铁,将我们的View也来完善下,也让整个系列相对完整,博主不希望烂尾. ...

  9. mvc-自定义视图引擎

    //自定义视图引擎的实质是把数据模型(moudle)和模板(View)转换成html页面,输出到客户端public class MyView:IView { string _viewPath; pub ...

随机推荐

  1. 一个空行引起的阿里云负载均衡上部署https证书的问题

    今天在阿里云上购买了WoSign的https证书,在证书签发后,在控制台下载证书文件,一共有2个文件,一个是.key文件(私钥文件),一个是.pem文件(证书文件). 然后在阿里云负载均衡“证书管理” ...

  2. 公共代码参考(Volley)

    Volley 是google提供的一个网络库,相对于自己写httpclient确实方便很多,本文参考部分网上例子整理如下,以作备忘: 定义一个缓存类: public class BitmapCache ...

  3. Aoite 系列(01) - 比 Dapper 更好用的 ORM

    Aoite 是一个适于任何 .Net Framework 4.0+ 项目的快速开发整体解决方案.Aoite.Data 适用于市面上大多数的数据库提供程序,通过统一封装,可以在日常开发中简单便捷的操作数 ...

  4. 生成PDF的新选择-Phantomjs

    最近在node.js项目开发中,遇见生成PDF的需求,当然生成PDF不是一个新意的需求:我可以选择利用开源的pdfkit或者其他node pdf模块,或者通过edge.js调用.net/python下 ...

  5. Homebrew简介及安装

    Homebrew官网 http://brew.sh/index_zh-cn.html Homebrew是神马 linux系统有个让人蛋疼的通病,软件包依赖,好在当前主流的两大发行版本都自带了解决方案, ...

  6. 启发式算法(Heuristic Algorithm)

    背景: 李航的<统计学习方法>一书中提到:决策树算法通常采用启发式算法,故了解之 问题解答: 时间有限,这里也只是将算法和启发式算法的区别和简单发展摘录如下: 一.算法和启发式方法之间的差 ...

  7. EF架构~在Linq to Entity中使用日期函數

    回到目录 眾所周知,在linq to entity的查询语句中,不允许出现ef不能识别的关键字,如Trim,Substring,TotalDays等.net里的关键字,在EF查询里都是不被支持的,它的 ...

  8. jQuery对 动态添加 的元素 绑定事件(on()的用法)

    从jQuery 版本 1.7 起,on() 方法是向被选元素添加事件处理程序的(官方推荐)首选方法. 当浏览器下载完一个页面的时候就开始渲染(翻译)HTML标签,然后执行css.js代码,在执行js代 ...

  9. 让 “微软雅黑” 在IE6下完美显示

    微软雅黑可以说是网页中最常见的字体了,但是往往在IE8+的浏览器上调试得很好,到了IE6则会变成默认的"宋体". 因为宋体字体宽度通常比雅黑的要宽,有时候会把页面都布局挤乱. 为了 ...

  10. lufylegend游戏引擎

    lufylegend游戏引擎介绍:click 这个链接我觉得已经很详细的介绍了这个引擎. 所以以下我只说说一些简单的游戏代码过程. 首先从canvas做游戏叙述起: 这是一个让人很熟悉的简单小游戏,网 ...