欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

ASP.NET MVC重写RazorViewEngine实现多主题切换

程序员文章站 2023-10-30 14:43:28
在asp.net mvc中来实现主题的切换一般有两种方式,一种是通过切换皮肤的css和js引用,一种就是通过重写视图引擎。通过重写视图引擎的方式更加灵活,因为我不仅可以在不...

在asp.net mvc中来实现主题的切换一般有两种方式,一种是通过切换皮肤的css和js引用,一种就是通过重写视图引擎。通过重写视图引擎的方式更加灵活,因为我不仅可以在不同主题下面布局和样式不一样,还可以让不同的主题下面显示的数据条目不一致,就是说可以在某些主题下面添加一下个性化的东西。

本篇我将通过重写视图引擎的方式来进行演示,在这之前,我假设你已经具备了mvc的一些基础,我们先来看下效果:

系统登录后是默认主题,当我们点击切换主题之后,左侧菜单栏的布局变了,右侧内容的样式也变了,而地址栏是不变的。界面ui用的metronic,虽然官网是收费的,但是在天朝,总是可以找到免费的。官方地址:

ASP.NET MVC重写RazorViewEngine实现多主题切换

ASP.NET MVC重写RazorViewEngine实现多主题切换

在这里,我使用了分区域、分模块(按独立的业务功能划分)的方式,一个模块就是一个独立的dll,在这里secom.emx.admin和secom.emx.history就是两个独立的模块,并分别创建了区域admin和history。

ASP.NET MVC重写RazorViewEngine实现多主题切换ASP.NET MVC重写RazorViewEngine实现多主题切换

你会发现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项目的指定目录下。

ASP.NET MVC重写RazorViewEngine实现多主题切换

区域配置文件我放置到了secom.emx.webapp中,其实你完全可以独立放置到一个类库项目中,因为注册区域路由的后,项目最终会寻找bin目录下面所有继承了arearegistration类的,然后让webapp引用这个类库项目,secom.emx.webapp项目添加secom.emx.admin、secom.emx.history的引用。

ASP.NET MVC重写RazorViewEngine实现多主题切换

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"};
   }

  }
 }
}

ASP.NET MVC重写RazorViewEngine实现多主题切换

重写后,我们的路由规则将是这样的:当没有选择主题的情况下,沿用原来的路由规则,如果选择了主题,则使用重写后的路由规则。

新的路由规则:在选择了主题的情况下,优先查找thems/主题名称/views/areas/区域名称/控制器名称/视图名称.cshtml,如果找不到再按照默认的路由规则去寻找,也就是areas/区域名称/views/控制器名称/视图名称.cshtml

ASP.NET MVC重写RazorViewEngine实现多主题切换

切换主题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));
   }
  }
 }
}

实现方式实在是太简单,简单得我不知道如何表述才好,我还是记下来,方便有需要的人可以查阅,希望可以帮到你们。由于项目引入了庞大的各种相关文件以致文件比较大,网速原因无法上传源码还望见谅!