ASP.NET MVC重寫RazorViewEngine實(shí)現(xiàn)多主題切換
在ASP.NET MVC中來實(shí)現(xiàn)主題的切換一般有兩種方式,一種是通過切換皮膚的css和js引用,一種就是通過重寫視圖引擎。通過重寫視圖引擎的方式更加靈活,因?yàn)槲也粌H可以在不同主題下面布局和樣式不一樣,還可以讓不同的主題下面顯示的數(shù)據(jù)條目不一致,就是說可以在某些主題下面添加一下個(gè)性化的東西。
本篇我將通過重寫視圖引擎的方式來進(jìn)行演示,在這之前,我假設(shè)你已經(jīng)具備了MVC的一些基礎(chǔ),我們先來看下效果:
系統(tǒng)登錄后是默認(rèn)主題,當(dāng)我們點(diǎn)擊切換主題之后,左側(cè)菜單欄的布局變了,右側(cè)內(nèi)容的樣式也變了,而地址欄是不變的。界面UI用的metronic,雖然官網(wǎng)是收費(fèi)的,但是在天朝,總是可以找到免費(fèi)的。官方地址:http://keenthemes.com/preview/metronic/


在這里,我使用了分區(qū)域、分模塊(按獨(dú)立的業(yè)務(wù)功能劃分)的方式,一個(gè)模塊就是一個(gè)獨(dú)立的dll,在這里Secom.Emx.Admin和Secom.Emx.History就是兩個(gè)獨(dú)立的模塊,并分別創(chuàng)建了區(qū)域Admin和History。


你會(huì)發(fā)現(xiàn)Secom.Emx.Admin模型下面的Areas目錄和Secom.Emx.WebApp中的目錄是一模一樣的,其實(shí)我最初不想在模塊項(xiàng)目中添加任何的View,但是為了方便獨(dú)立部署還是加了。
右鍵單擊項(xiàng)目Secom.Emx.Admin,選擇“屬性”——“生成事件”添加如下代碼:
xcopy /e/r/y $(ProjectDir)Areas\Admin\Views $(SolutionDir)Secom.Emx.WebApp\Areas\Admin\Views
這命令很簡(jiǎn)單,其實(shí)就是當(dāng)編譯項(xiàng)目Secom.Emx.Admin的時(shí)候,將項(xiàng)目中的Views復(fù)制到Secom.Emx.WebApp項(xiàng)目的指定目錄下。

區(qū)域配置文件我放置到了Secom.Emx.WebApp中,其實(shí)你完全可以獨(dú)立放置到一個(gè)類庫項(xiàng)目中,因?yàn)樽?cè)區(qū)域路由的后,項(xiàng)目最終會(huì)尋找bin目錄下面所有繼承了AreaRegistration類的,然后讓W(xué)ebApp引用這個(gè)類庫項(xiàng)目,Secom.Emx.WebApp項(xiàng)目添加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" },這個(gè)命名空間就是獨(dú)立模塊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的原始構(gòu)造函數(shù)如下:
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,對(duì)View的路由規(guī)則進(jìn)行了重寫,既然可以重寫路由規(guī)則,那意味著,你可以任意定義規(guī)則,然后遵守自己定義的規(guī)則就可以了。需要注意的是,要注意路由數(shù)組中的順序,查找視圖時(shí),是按照前后順序依次查找的,當(dāng)找到了視圖就立即返回,不會(huì)再去匹配后面的路由規(guī)則。為了提升路由查找效率,我這里刪除了所有vbhtml的路由規(guī)則,因?yàn)槲艺麄€(gè)項(xiàng)目中都采用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"};
}
}
}
}

重寫后,我們的路由規(guī)則將是這樣的:當(dāng)沒有選擇主題的情況下,沿用原來的路由規(guī)則,如果選擇了主題,則使用重寫后的路由規(guī)則。
新的路由規(guī)則:在選擇了主題的情況下,優(yōu)先查找thems/主題名稱/views/Areas/區(qū)域名稱/控制器名稱/視圖名稱.cshtml,如果找不到再按照默認(rèn)的路由規(guī)則去尋找,也就是Areas/區(qū)域名稱/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> 默認(rèn)主題
</a>
</li>
<li>
<a href="javascript:setTheme('Blue')">
<i class="icon-tag"></i> 藍(lán)色主題
</a>
</li>
</ul>
</div>
<script type="text/javascript">
function setTheme(themeName)
{
window.location.href = "/Home/SetTheme?themeName=" + themeName + "&href=" + window.location.href;
}
</script>
當(dāng)用戶登錄成功的時(shí)候,從Cookie中讀取所選主題信息,當(dāng)Cookie中沒有讀取到主題記錄時(shí),則從Web.config配置文件中讀取配置的主題名稱,如果都沒有讀取到,則說明是默認(rèn)主題,沿用原有的視圖引擎規(guī)則。
在后臺(tái)管理界面,每次選擇了主題,我都將主題名稱存儲(chǔ)到Cookie中,默認(rèn)保存一年,這樣當(dāng)下次再登錄的時(shí)候,就能夠記住所選的主題信息了。
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;
}
//模板風(fēng)格
_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));
}
}
}
}
實(shí)現(xiàn)方式實(shí)在是太簡(jiǎn)單,簡(jiǎn)單得我不知道如何表述才好,我還是記下來,方便有需要的人可以查閱,希望可以幫到你們。由于項(xiàng)目引入了龐大的各種相關(guān)文件以致文件比較大,網(wǎng)速原因無法上傳源碼還望見諒!
相關(guān)文章
asp.net中讓Repeater和GridView支持DataPager分頁
.NET 3.5中的DataPager碉堡了,可惜只支持ListView。傳統(tǒng)的GridView和Repeater都無法直接使用DataPager分頁。但我們?nèi)绻约痈脑?,就可以讓Repeater和GridView支持DataPager分頁2012-02-02
gridview checkbox從服務(wù)器端和客戶端兩個(gè)方面實(shí)現(xiàn)全選和反選
GridView中的checkbox的全選和反選在很多的地方都是要求實(shí)現(xiàn)的,所以下面就從服務(wù)器端和客戶端兩個(gè)方面實(shí)現(xiàn)了checkbox的選擇,感興趣的朋友可以了解下,希望本文對(duì)你有所幫助2013-01-01
記一次EFCore類型轉(zhuǎn)換錯(cuò)誤及解決方案
這篇文章主要介紹了記一次EFCore類型轉(zhuǎn)換錯(cuò)誤及解決方案,幫助大家更好的理解和學(xué)習(xí)使用asp.net core,感興趣的朋友可以了解下2021-03-03
Json數(shù)據(jù)轉(zhuǎn)換list對(duì)象實(shí)現(xiàn)思路及代碼
本文為大家詳細(xì)介紹下Json數(shù)據(jù)轉(zhuǎn)換list對(duì)象的具體實(shí)現(xiàn),感興趣的朋友可以參考下哈,希望對(duì)你有所幫助2013-04-04
ASP.NET MVC DropDownList數(shù)據(jù)綁定及使用詳解
DropDownList 控件用于創(chuàng)建下拉列表。DropDownList 控件中的每個(gè)可選項(xiàng)都是由 ListItem 元素定義的!該控件支持?jǐn)?shù)據(jù)綁定2012-12-12
基于Asp.Net MVC4 Bundle捆綁壓縮技術(shù)的介紹
本篇文章,小編將為大家介紹,Asp.Net MVC4 Bundle捆綁壓縮技術(shù),有需要的朋友可以參考一下2013-04-04

