ASP.NET MVC 重寫RazorViewEngine實現多主題切換

    在ASP.NET MVC中來實現主題的切換通常有兩種方式,一種是經過切換皮膚的css和js引用,一種就是經過重寫視圖引擎。經過重寫視圖引擎的方式更加靈活,由於我不只能夠在不一樣主題下面佈局和樣式不同,還可讓不一樣的主題下面顯示的數據條目不一致,就是說能夠在某些主題下面添加一下個性化的東西。javascript

    本篇我將經過重寫視圖引擎的方式來進行演示,在這以前,我假設你已經具有了MVC的一些基礎,系統登陸後是默認主題,當咱們點擊切換主題以後,左側菜單欄的佈局變了,右側內容的樣式也變了,而地址欄是不變的。界面UI用的metronic,雖然官網是收費的,可是在天朝,老是能夠找到免費的。metronic是基於bootstrap的UI框架,官網地址:http://keenthemes.com/preview/metronic/css

    咱們先來看下效果:html

    在這裏,我使用了分區域、分模塊(按獨立的業務功能劃分)的方式,一個模塊就是一個獨立的dll,在這裏Secom.Emx.Admin和Secom.Emx.History就是兩個獨立的模塊,並分別建立了區域Admin和History,固然你能夠在獨立模塊下面建立多個區域。java

 

    你會發現Secom.Emx.Admin模型下面的Areas目錄和Secom.Emx.WebApp中的目錄是如出一轍的,其實我最初不想在模塊項目中添加任何的View,可是爲了方便獨立部署仍是加了。右鍵單擊項目Secom.Emx.Admin,選擇「屬性」——「生成事件」添加以下代碼:bootstrap

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的引用。mvc

    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代碼以下:asp.net

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" }
            );
        }
    }
}
View Code

    咱們先看下RazorViewEngine的原始構造函數以下:ide

    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",  
        };  
    }  
View Code

    而後新建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"};
            }

        }
    }
}
View Code

    重寫後,咱們的路由規則將是這樣的:當沒有選擇主題的狀況下,沿用原來的路由規則,若是選擇了主題,則使用重寫後的路由規則。

新的路由規則:在選擇了主題的狀況下,優先查找thems/主題名稱/views/Areas/區域名稱/控制器名稱/視圖名稱.cshtml,若是找不到再按照默認的路由規則去尋找,也就是Areas/區域名稱/Views/控制器名稱/視圖名稱.cshtml。

能夠看到咱們查找模板頁的方式也被修改了,因此對於一些通用的,只要換模板頁就能夠了,不須要添加view界面,由於指定主題下面找不到view會去默認主題下面找,而view界面會引用模板頁的,對於一些個性化的東西,再去指定的主題下面添加新的view,不知道我這樣表述你有明白沒,感受比較饒,反正就是你能夠按照你本身的規則去找視圖,而不是asp.net mvc默認的規則。

 

    切換主題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>&nbsp;
                        <span class="hidden-sm hidden-xs">切換主題&nbsp;</span>&nbsp;
                        <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));
            }
        }
    }
}
View Code

     實現方式實在是太簡單,簡單得我不知道如何表述纔好,我仍是記下來,方便有須要的人能夠查閱,但願能夠幫到大家。因爲項目引入了龐大的各類相關文件以至文件比較大,網速緣由沒法上傳源碼還望見諒!

相關文章
相關標籤/搜索