ASP.NET MVC重写RazorViewEngine实现多主题切换
发布时间 - 2026-01-11 02:05:46 点击率:次在ASP.NET MVC中来实现主题的切换一般有两种方式,一种是通过切换皮肤的css和js引用,一种就是通过重写视图引擎。通过重写视图引擎的方式更加灵活,因为我不仅可以在不同主题下面布局和样式不一样,还可以让不同的主题下面显示的数据条目不一致,就是说可以在某些主题下面添加一下个性化的东西。

本篇我将通过重写视图引擎的方式来进行演示,在这之前,我假设你已经具备了MVC的一些基础,我们先来看下效果:
系统登录后是默认主题,当我们点击切换主题之后,左侧菜单栏的布局变了,右侧内容的样式也变了,而地址栏是不变的。界面UI用的metronic,虽然官网是收费的,但是在天朝,总是可以找到免费的。官方地址:http://keenthemes.com/preview/metronic/
在这里,我使用了分区域、分模块(按独立的业务功能划分)的方式,一个模块就是一个独立的dll,在这里Secom.Emx.Admin和Secom.Emx.History就是两个独立的模块,并分别创建了区域Admin和History。
你会发现Secom.Emx.Admin模型下面的Areas目录和Secom.Emx.WebApp中的目录是一模一样的,其实我最初不想在模块项目中添加任何的View,但是为了方便独立部署还是加了。
右键单击项目Secom.Emx.Admin,选择“属性”——“生成事件”添加如下代码:
xcopy /e/r/y $(ProjectDir)Areas\Admin\Views $(SolutionDir)Secom.Emx.WebApp\Areas\Admin\Views
这命令很简单,其实就是当编译项目Secom.Emx.Admin的时候,将项目中的Views复制到Secom.Emx.WebApp项目的指定目录下。
区域配置文件我放置到了Secom.Emx.WebApp中,其实你完全可以独立放置到一个类库项目中,因为注册区域路由的后,项目最终会寻找bin目录下面所有继承了AreaRegistration类的,然后让WebApp引用这个类库项目,Secom.Emx.WebApp项目添加Secom.Emx.Admin、Secom.Emx.History的引用。
AdminAreaRegistration代码如下:
using System.Web.Mvc;
namespace Secom.Emx.WebApp
{
public class AdminAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Admin";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Admin_default",
"Admin/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
namespaces:new string[1] { "Secom.Emx.Admin.Areas.Admin.Controllers" }
);
}
}
}
注意命名空间和后面添加的 namespaces:new string[1] { "Secom.Emx.Admin.Areas.Admin.Controllers" },这个命名空间就是独立模块Secom.Emx.Admin下面的控制器所在的命名空间。
HistoryAreaRegistration代码如下:
using System.Web.Mvc;
namespace Secom.Emx.WebApp
{
public class HistoryAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "History";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"History_default",
"History/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
namespaces:new string[1] { "Secom.Emx.History.Areas.History.Controllers" }
);
}
}
}
我们先看下RazorViewEngine的原始构造函数如下:
public RazorViewEngine(IViewPageActivator viewPageActivator)
: base(viewPageActivator)
{
AreaViewLocationFormats = new[]
{
"~/Areas/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{2}/Views/{1}/{0}.vbhtml",
"~/Areas/{2}/Views/Shared/{0}.cshtml",
"~/Areas/{2}/Views/Shared/{0}.vbhtml"
};
AreaMasterLocationFormats = new[]
{
"~/Areas/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{2}/Views/{1}/{0}.vbhtml",
"~/Areas/{2}/Views/Shared/{0}.cshtml",
"~/Areas/{2}/Views/Shared/{0}.vbhtml"
};
AreaPartialViewLocationFormats = new[]
{
"~/Areas/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{2}/Views/{1}/{0}.vbhtml",
"~/Areas/{2}/Views/Shared/{0}.cshtml",
"~/Areas/{2}/Views/Shared/{0}.vbhtml"
};
ViewLocationFormats = new[]
{
"~/Views/{1}/{0}.cshtml",
"~/Views/{1}/{0}.vbhtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/{0}.vbhtml"
};
MasterLocationFormats = new[]
{
"~/Views/{1}/{0}.cshtml",
"~/Views/{1}/{0}.vbhtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/{0}.vbhtml"
};
PartialViewLocationFormats = new[]
{
"~/Views/{1}/{0}.cshtml",
"~/Views/{1}/{0}.vbhtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/{0}.vbhtml"
};
FileExtensions = new[]
{
"cshtml",
"vbhtml",
};
}
然后新建CustomRazorViewEngine继承自RazorViewEngine,对View的路由规则进行了重写,既然可以重写路由规则,那意味着,你可以任意定义规则,然后遵守自己定义的规则就可以了。需要注意的是,要注意路由数组中的顺序,查找视图时,是按照前后顺序依次查找的,当找到了视图就立即返回,不会再去匹配后面的路由规则。为了提升路由查找效率,我这里删除了所有vbhtml的路由规则,因为我整个项目中都采用C#语言。
using System.Web.Mvc;
namespace Secom.Emx.WebApp.Helper
{
public class CustomRazorViewEngine : RazorViewEngine
{
public CustomRazorViewEngine(string theme)
{
if (!string.IsNullOrEmpty(theme))
{
AreaViewLocationFormats = new[]
{
//themes
"~/themes/"+theme+"/views/Areas/{2}/{1}/{0}.cshtml",
"~/themes/"+theme+"/Shared/{0}.cshtml"
"~/Areas/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{2}/Views/Shared/{0}.cshtml"
};
AreaMasterLocationFormats = new[]
{
//themes
"~/themes/"+theme+"/views/Areas/{2}/{1}/{0}.cshtml",
"~/themes/"+theme+"/views/Areas/{2}/Shared/{0}.cshtml",
"~/themes/"+theme+"/views/Shared/{0}.cshtml",
"~/Areas/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{2}/Views/Shared/{0}.cshtml"
};
AreaPartialViewLocationFormats = new[]
{
//themes
"~/themes/"+theme+"/views/Shared/{0}.cshtml",
"~/Areas/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{2}/Views/Shared/{0}.cshtml"
};
ViewLocationFormats = new[]
{
//themes
"~/themes/"+theme+"/views/{1}/{0}.cshtml",
"~/Views/{1}/{0}.cshtml",
"~/Views/Shared/{0}.cshtml"
};
MasterLocationFormats = new[]
{
//themes
"~/themes/"+theme+"/views/Shared/{0}.cshtml",
"~/Views/{1}/{0}.cshtml",
"~/Views/Shared/{0}.cshtml"
};
PartialViewLocationFormats = new[]
{
//themes
"~/themes/"+theme+"/views/Shared/{0}.cshtml",
"~/Views/{1}/{0}.cshtml",
"~/Views/Shared/{0}.cshtml"
};
FileExtensions = new[]{"cshtml"};
}
}
}
}
重写后,我们的路由规则将是这样的:当没有选择主题的情况下,沿用原来的路由规则,如果选择了主题,则使用重写后的路由规则。
新的路由规则:在选择了主题的情况下,优先查找thems/主题名称/views/Areas/区域名称/控制器名称/视图名称.cshtml,如果找不到再按照默认的路由规则去寻找,也就是Areas/区域名称/Views/控制器名称/视图名称.cshtml
切换主题View代码:
<div class="btn-group">
<button type="button" class="btn btn-circle btn-outline red dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-plus"></i>
<span class="hidden-sm hidden-xs">切换主题 </span>
<i class="fa fa-angle-down"></i>
</button>
<ul class="dropdown-menu" role="menu">
<li>
<a href="javascript:setTheme('default')">
<i class="icon-docs"></i> 默认主题
</a>
</li>
<li>
<a href="javascript:setTheme('Blue')">
<i class="icon-tag"></i> 蓝色主题
</a>
</li>
</ul>
</div>
<script type="text/javascript">
function setTheme(themeName)
{
window.location.href = "/Home/SetTheme?themeName=" + themeName + "&href=" + window.location.href;
}
</script>
当用户登录成功的时候,从Cookie中读取所选主题信息,当Cookie中没有读取到主题记录时,则从Web.config配置文件中读取配置的主题名称,如果都没有读取到,则说明是默认主题,沿用原有的视图引擎规则。
在后台管理界面,每次选择了主题,我都将主题名称存储到Cookie中,默认保存一年,这样当下次再登录的时候,就能够记住所选的主题信息了。
using System;
using System.Web.Mvc;
using Secom.Emx.WebApp.Helper;
using System.Web;
using Secom.Emx.Common.Controllers;
namespace Secom.Emx.WebApp.Controllers
{
public class HomeController : BaseController
{
string themeCookieName = "Theme";
public ActionResult Index()
{
ViewData["Menu"] = GetMenus();
return View();
}
public ActionResult SetTheme(string themeName,string href)
{
if (!string.IsNullOrEmpty(themeName))
{
Response.Cookies.Set(new HttpCookie(themeCookieName, themeName) { Expires = DateTime.Now.AddYears(1) });
}
else
{
themeName = Request.Cookies[themeCookieName].Value ?? "".Trim();
}
Utils.ResetRazorViewEngine(themeName);
return string.IsNullOrEmpty(href)? Redirect("~/Home/Index"):Redirect(href);
}
public ActionResult Login()
{
string themeName = Request.Cookies[themeCookieName].Value ?? "".Trim();
if (!string.IsNullOrEmpty(themeName))
{
Utils.ResetRazorViewEngine(themeName);
}
return View();
}
}
}
Utils类:
using System.Configuration;
using System.Web.Mvc;
namespace Secom.Emx.WebApp.Helper
{
public class Utils
{
private static string _themeName;
public static string ThemeName
{
get
{
if (!string.IsNullOrEmpty(_themeName))
{
return _themeName;
}
//模板风格
_themeName =string.IsNullOrEmpty(ConfigurationManager.AppSettings["Theme"])? "" : ConfigurationManager.AppSettings["Theme"];
return _themeName;
}
}
public static void ResetRazorViewEngine(string themeName)
{
themeName = string.IsNullOrEmpty(themeName) ? Utils.ThemeName : themeName;
if (!string.IsNullOrEmpty(themeName))
{
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new CustomRazorViewEngine(themeName));
}
}
}
}
实现方式实在是太简单,简单得我不知道如何表述才好,我还是记下来,方便有需要的人可以查阅,希望可以帮到你们。由于项目引入了庞大的各种相关文件以致文件比较大,网速原因无法上传源码还望见谅!
# ASP.NET
# MVC
# RazorViewEngine
# 主题切换
# 详解ASP.NET Razor 语法
# 详解ASP.NET MVC 利用Razor引擎生成静态页
# ASP.NET MVC4 Razor模板简易分页效果
# ASP.NET Razor模板引擎中输出Html的两种方式
# ASP.NET MVC使用RazorEngine解析模板生成静态页
# asp.net模板引擎Razor调用外部方法用法实例
# asp.net模板引擎Razor中cacheName的问题分析
# 使用Asp.net Mvc3 Razor视图方式扩展JQuery UI Widgets方法介绍
# 详细分析ASP.NET Razor之C# 变量
# 重写
# 在这里
# 选择了
# 配置文件
# 所选
# 的人
# 的是
# 类库
# 情况下
# 因为我
# 我不
# 还可以
# 你可以
# 在这
# 找不到
# 右键
# 要注意
# 将是
# 很简单
# 我将
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
详解阿里云nginx服务器多站点的配置
如何在云服务器上快速搭建个人网站?
无锡营销型网站制作公司,无锡网选车牌流程?
Laravel怎么集成Log日志记录_Laravel单文件与每日日志配置及自定义通道【详解】
Linux系统命令中tree命令详解
Windows家庭版如何开启组策略(gpedit.msc)?(安装方法)
小视频制作网站有哪些,有什么看国内小视频的网站,求推荐?
android nfc常用标签读取总结
iOS发送验证码倒计时应用
Laravel数据库迁移怎么用_Laravel Migration管理数据库结构的正确姿势
BootStrap整体框架之基础布局组件
如何使用 Go 正则表达式精准提取括号内首个纯字母标识符(忽略数字与嵌套)
大连网站制作公司哪家好一点,大连买房网站哪个好?
Python文件流缓冲机制_IO性能解析【教程】
如何在宝塔面板中修改默认建站目录?
如何在宝塔面板创建新站点?
如何确保FTP站点访问权限与数据传输安全?
Linux后台任务运行方法_nohup与&使用技巧【技巧】
linux top下的 minerd 木马清除方法
如何在Ubuntu系统下快速搭建WordPress个人网站?
教你用AI将一段旋律扩展成一首完整的曲子
Javascript中的事件循环是如何工作的_如何利用Javascript事件循环优化异步代码?
HTML5空格在Angular项目里怎么处理_Angular中空格的渲染问题【详解】
UC浏览器如何设置启动页 UC浏览器启动页设置方法
PHP正则匹配日期和时间(时间戳转换)的实例代码
关于BootStrap modal 在IOS9中不能弹出的解决方法(IOS 9 bootstrap modal ios 9 noticework)
如何用PHP工具快速搭建高效网站?
高端建站三要素:定制模板、企业官网与响应式设计优化
JavaScript如何实现继承_有哪些常用方法
专业型网站制作公司有哪些,我设计专业的,谁给推荐几个设计师兼职类的网站?
canvas 画布在主流浏览器中的尺寸限制详细介绍
Firefox Developer Edition开发者版本入口
Python函数文档自动校验_规范解析【教程】
Laravel模型事件有哪些_Laravel Model Event生命周期详解
Laravel如何正确地在控制器和模型之间分配逻辑_Laravel代码职责分离与架构建议
Win11怎么设置默认图片查看器_Windows11照片应用关联设置
Laravel如何使用Service Provider注册服务_Laravel服务提供者配置与加载
Laravel中DTO是什么概念_在Laravel项目中使用数据传输对象(DTO)
如何快速搭建个人网站并优化SEO?
Laravel怎么做数据加密_Laravel内置Crypt门面的加密与解密功能
Laravel如何发送系统通知?(Notification渠道示例)
Laravel如何配置和使用队列处理异步任务_Laravel队列驱动与任务分发实例
Android滚轮选择时间控件使用详解
千问怎样用提示词获取健康建议_千问健康类提示词注意事项【指南】
如何快速生成可下载的建站源码工具?
Laravel如何为API编写文档_Laravel API文档生成与维护方法
ChatGPT 4.0官网入口地址 ChatGPT在线体验官网
Laravel如何与Vue.js集成_Laravel + Vue前后端分离项目搭建指南
如何快速生成ASP一键建站模板并优化安全性?
JavaScript实现Fly Bird小游戏

