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

一个网站的主题包括布局,色调,内容展示等,每种主题在某些方面应该或多或少不一样的,否则就不能称之为不同的主题了。每一个网站至少都有一个主题,我这里称之为默认主题,也就是我们平常开发设计网站时的一个固定布局,固定色调,固定内容展示等构成一个默认的固定主题。单一主题针对一些小网站或网站用户群体相对单一固定还是比较适用的,但如果是大型网站或是网站的用户群体比较多而且复杂,如:京东博客园里的每个博客空间、文俊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

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

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

时间: 2024-09-29 00:33:06

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

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

ASP.NET mvc的razor视图引擎是一个非常好的.NET MVC框架内置的视图引擎.一般情况我们使用.NET MVC框架为我们提供的这个Razor视图引擎就足够了.但是有时我们想在我们的项目支持多模板&skins机制,比如我们可能会有多套的模板,也就是多个View风格,而我们只需要改一下配置文件就可以轻松的改变页面的风格和模板.实现这个功能有两种方式: 一.使用接口IViewEngine自己完成一个类似Razor视图引擎的功能. 二.继承类RazorViewEngine类,重写它的一些方

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

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

MVC自定义视图引擎地址

先看结构 1.RouteConfig 文件(注意顺序) public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "Manage_Default", url: "Manage/{controller}/{action}/{id}", defa

MVC系列——MVC源码学习:打造自己的MVC框架(四:自定义视图)

前言:通过之前的三篇介绍,我们基本上完成了从请求发出到路由匹配.再到控制器的激活,再到Action的执行这些个过程.今天还是趁热打铁,将我们的View也来完善下,也让整个系列相对完整,博主不希望烂尾.对于这个系列,通过学习源码,博主也学到了很多东西,在此还是把博主知道的先发出来,供大家参考. 本文原创地址:http://www.cnblogs.com/landeanfen/p/6019719.html MVC源码学习系列文章目录: MVC系列——MVC源码学习:打造自己的MVC框架(一) MVC

ASP.NET 视图引擎

ASP.NET 中从MVC3.0 开始引入可Razor视图引擎.而遗留的ASPX引擎对维护与旧版的MVC程序是用的. 一.视图引擎是实现了IViewEngine接口,View Engine的作用是把对View的请求转换成ViewEngineResult对象,下面是此接口的定义: namespace System.Web.Mvc { public interface IViewEngine { ViewEngineResult FindPartialView(ControllerContext c

ASP.NET Core 自定义视图路径及主题切换

原文地址:https://www.cnblogs.com/ElderJames/p/Customized-View-Path-And-Theme-Switching-In-AspNetCore.html <ASP.NET Core 中的SEO优化(1):中间件实现服务端静态化缓存> <ASP.NET Core 中的SEO优化(2):中间件中渲染Razor视图> <ASP.NET Core 中的SEO优化(3):自定义路由匹配和生成> 0|1背景 切换主题,是博客.CMS

ASP.Net MVC开发基础学习笔记(3):Razor视图引擎、控制器与路由机制学习

首页 头条 文章 频道                         设计频道 Web前端 Python开发 Java技术 Android应用 iOS应用 资源 小组 相亲 频道 首页 头条 文章 小组 相亲 资源 设计 前端 Python Java 安卓 iOS 登录 注册 首页 最新文章 经典回顾 开发 Web前端 Python Android iOS Java C/C++ PHP .NET Ruby Go 设计 UI设计 网页设计 交互设计 用户体验 设计教程 设计职场 极客 IT技术

ASP.Net MVC开发基础学习笔记:三、Razor视图引擎、控制器与路由机制学习

一.天降神器“剃须刀” — Razor视图引擎 1.1 千呼万唤始出来的MVC3.0 在MVC3.0版本的时候,微软终于引入了第二种模板引擎:Razor.在这之前,我们一直在使用WebForm时代沿留下来的ASPX引擎或者第三方的NVelocity模板引擎. Razor在减少代码冗余.增强代码可读性和Visual Studio智能感知方面,都有着突出的优势.Razor一经推出就深受广大ASP.Net开发者的喜爱. 1.2 Razor的语法 (1)Razor文件类型:Razor支持两种文件类型,分

asp.net mvc视图引擎

继上周介绍了Razor之后,ASP.NET MVC 现在已有四种主要的视图引擎.其他三种引擎是Spark.NHaml和传统的ASPX文件模板.本文将大致介绍这四种引擎,并着重讨论新的Razor引 擎. ASPX风格的视图引擎可以追溯到遥远的ASP.使 用<%= %>和<%: %>语法的占位符在这类风格中占据了统治地位.随着时间的推移,ASPC控件被加入进来,之后是母版页(Master Page),但这同时也带来 了昂贵的页面生命周期. Spark,在Castle项目的MonoRai