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

在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));
   }
  }
 }
}

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

(0)

相关推荐

  • NopCommerce架构分析之(六)自定义RazorViewEngine和WebViewPage

    系统中对Razor的支持包括两部分,其中之一就是自定义RazorViewEngine 一.自定义RazorViewEngine 在Global.asax.cs的Application_Start方法中,注册了自定义视图引擎: //remove all view engines ViewEngines.Engines.Clear(); //except the themeable razor view engine we use ViewEngines.Engines.Add(new Themea

  • ASP.NET 主题的简单配置教程

    主题和css差不多但是他可以提供一些css不能提供的特性 ---------------主题是基于控件而不是HTML(主题允许定和和重用几乎所有的属性) ---------------主题应用在服务器上 ---------------可以通过配置文件来应用主题 ---------------主题不会像css那样重叠(如果一个主题和空间里同时定义了属性,那么主题的定义会覆盖控件的属性,当然这个也可以修改) 主题里面的控件不能定义ID 简单实例 1.网站---添加新项---外观文件(后缀名为.ski

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

    在ASP.NET MVC中来实现主题的切换一般有两种方式,一种是通过切换皮肤的css和js引用,一种就是通过重写视图引擎.通过重写视图引擎的方式更加灵活,因为我不仅可以在不同主题下面布局和样式不一样,还可以让不同的主题下面显示的数据条目不一致,就是说可以在某些主题下面添加一下个性化的东西. 本篇我将通过重写视图引擎的方式来进行演示,在这之前,我假设你已经具备了MVC的一些基础,我们先来看下效果: 系统登录后是默认主题,当我们点击切换主题之后,左侧菜单栏的布局变了,右侧内容的样式也变了,而地址栏是

  • 详解ASP.NET MVC 解析模板生成静态页(RazorEngine)

    简述 Razor是ASP.NET MVC 3中新加入的技术,以作为ASPX引擎的一个新的替代项.在早期的MVC版本中默认使用的是ASPX模板引擎,Razor在语法上的确不错,用起来非常方便,简洁的语法与.NET Framework 结合,广泛应用于ASP.NET MVC 项目. 我们在很多项目开发中会常常用到页面静态化,页面静态化有许多方式,最常见的就是类似很多PHP CMS种使用的 标签替换的方式(如:帝国CMS.EcShop等),还有很多都是伪静态,伪静态我们就不做过多解释,通过路由或Url

  • ASP.NET MVC小结之基础篇(二)

    整理除了这个笔记,共享一下子,基本MVC的所有东西都介绍了,但是都是很基础的东西.本来打算一篇发表完的,但是发现东西有点多,所以分成了两篇文章,这是最后一篇了! 1.ASP.NET MVC请求过程 1 2.Controller (1) 控制器在ASP.NET MVC中扮演着处理客户端请求的角色 1)必须实现System.Web.Mvc.IController接口 ->通常直接继承System.Web.MVC.Controller类 2)必须要以Controller结尾 3)通过不同的Action

  • 使用ASP.NET MVC引擎开发插件系统

    一.前言 我心中的插件系统应该是像Nop那样(更牛逼的如Orchard,OSGI.NET),每个插件模块不只是一堆实现了某个业务接口的dll,然后采用反射或IOC技术来调用,而是一个完整的mvc小应用,我可以在后台控制插件的安装和禁用,目录结构如下: 生成后放在站点根目录下的Plugins文件夹中,每个插件有一个子文件夹 Plugins/Sms.AliYun/ Plugins/Sms.ManDao/ 我是一个有强迫症的的懒人,我不想将生成的dll文件拷贝到bin目录. 二.要解决的问题 1.as

  • ASP.NET MVC小结之基础篇(一)

    前言:前几天要准备一个演讲,所以准备了MVC的一些基本的东西,以前也使用过MVC,但是只是使用,而不是去了解,所以趁着这个机会好好的把别人的MVC视频看了一下(是一个微软的MVP会员发布的视频,相信有些人都看过),整理除了这个笔记,共享一下子,基本MVC的所有东西都介绍了,但是都是很基础的东西.本来打算一篇发表完的,但是发现东西有点多,所以分成了两篇文章! 什么是ASP.NET MVC (1) ASP.NET MVC是微软官方提供的MVC模式编写ASP.NET Web应用程序的一个框架 (2)M

  • ASP.NET MVC 3仿Server.Transfer效果的实现方法

    当我们在使用ASP.NET MVC实现页面跳转的时候,常用的应该是: Redirect RedirectToAction RedirectToRoute 或者在前台使用脚本跳转. 但这几种跳转方式都是基于Get请求的,在某些特定场景下可能并不适用.例如需要传递大数据量参数.或者复杂对象类型参数的场景,get方式肯定是有限制的. 在webform里面,有一种服务器端跳转方式:Server.Transfer,相信大家一定都还记得.这种方式是中止当前页面执行,并将执行流程转入一个新的页面,并使用上一个

  • asp.net mvc webapi 实用的接口加密方法示例

    在很多项目中,因为webapi是对外开放的,这个时候,我们就要得考虑接口交换数据的安全性. 安全机制也比较多,如andriod与webapi 交换数据的时候,可以走双向证书方法,但是开发成本比较大, 今天我们不打算介绍这方面的知识,我们说说一个较简单也较常见的安全交换机制 在这里要提醒读者,目前所有的加密机制都不是绝对的安全! 我们的目标是,任何用户或者软件获取到我们的webapi接口url后用来再次访问该地址都是无效的! 达到这种目标的话,我们必须要在url中增加一个时间戳,但是仅仅如此还是不

  • Asp.net MVC中Razor常见的问题与解决方法总结

    前言 最近在学习Asp.net MVC Razor,在使用中遇到了不少的问题,所以想着总结下来,没有经验的童鞋就是这样磕磕碰碰出来的经验.话不多说,来一起看看详细的介绍: 一.Datatype的错误提示消息无法自定义 这也许是Asp.net MVC的一个Bug.ViewModel中定义了DataType为Date字段: [Required(ErrorMessage = "Birthday must be input!")] [DataType(DataType.Date, ErrorM

  • ASP.NET MVC下基于异常处理的完整解决方案总结

    EntLib的异常处理应用块(Exception Handling Application Block)是一个不错的异常处理框架,它使我们可以采用配置的方式来定义异常处理策略.而ASP.NET MVC是一个极具可扩展开发框架,在这篇文章中我将通过它的扩展实现与EntLib的集成,并提供一个完整的解决异常处理解决方案. 一.基本异常处理策略 我们首先来讨论我们的解决方案具体采用的异常处理策略: 对于执行Controller的某个Action方法抛出的异常,我们会按照指定配置策略进行处理.我们可以采

  • asp.net mvc发送邮件实例讲解

    QQ邮箱 POP3 和 SMTP 服务器地址设置如下:邮箱POP3服务器(端口110)SMTP服务器(端口25)qq.compop.qq.comsmtp.qq.comSMTP服务器需要身份验证. 如果是设置POP3和SMTP的SSL加密方式,则端口如下: POP3服务器(端口995) SMTP服务器(端口465或587). qq邮箱要注意开启下面的服务:qq会给你个授权码,在下面的代码中详细的讲了在哪用这个授权码. 控制器中代码: using System.Net.Mail;//邮件发送需引用

随机推荐