Asp.net MVC rewrites razorview engine to realize multi topic switching

Time:2021-7-21

There are two ways to switch topics in asp.net MVC, one is to switch CSS and JS references of skin, the other is to rewrite view engine. The way of rewriting the view engine is more flexible, because I can not only make the layout and style different under different themes, but also make the data items displayed under different themes inconsistent, that is, I can add something personalized under some themes.

In this article, I will demonstrate by rewriting the view engine. Before that, I assume that you have some basic knowledge of MVC. Let’s take a look at the effect

After the system logs in, it is the default theme. When we click to switch the theme, the layout of the left menu bar changes, the style of the right content changes, and the address bar remains unchanged. The interface UI uses Metronic. Although the official website charges for it, you can always find it free in China. Official address:http://keenthemes.com/preview/metronic/

Here, I use the method of sub region and sub module (divided by independent business functions). A module is an independent DLL. Here, secom.emx.admin and secom.emx.history are two independent modules, and regional admin and history are created respectively.

As like as two peas, you can see that the Areas directory under the Secom.Emx.Admin model is exactly the same as the directory in Secom.Emx.WebApp. Actually, I didn’t want to add any View to the module project at the beginning, but to facilitate the deployment or addition of the independent ones.

Right click the project secom.emx.admin, select “properties” – generate event, and add the following code:


xcopy /e/r/y $(ProjectDir)Areas\Admin\Views 
$(SolutionDir)Secom.Emx.WebApp\Areas\Admin\Views

This command is very simple. In fact, when compiling the secom.emx.admin project, copy the views in the project to the specified directory of the secom.emx.webapp project.

I put the region configuration file into secom.emx.webapp. In fact, you can put it into a class library project independently, because after registering the region routing, the project will eventually find all the inheritors of the arearegistration class in the bin directory, and then let webapp reference the class library project. The secom.emx.webapp project adds the references of secom.emx.admin and secom.emx.history.

The code of adminarearegistration is as follows:


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

Note the namespace and thenamespaces:new string[1] { “Secom.Emx.Admin.Areas.Admin.Controllers” }This namespace is a stand-alone moduleSecom.Emx.AdminThe following is the namespace of the controller.

The code of historyarearegistration is as follows:


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

Let’s first take a look at the original constructor of the razorviewengine as follows:


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

Then, the new customrazorviewengine inherits from the razorviewengine and rewrites the routing rules of view. Since you can rewrite the routing rules, it means that you can define any rules and then follow the rules you define. It should be noted that you should pay attention to the order in the routing array. When you look up the view, you should look up it in the order before and after. When you find the view, you will return immediately, and you will not match the following routing rules. In order to improve the efficiency of routing search, I delete all the vbhtml routing rules here, because I use C # language in my whole project.


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

  }
 }
}

After rewriting, our routing rules will be like this: when no topic is selected, the original routing rules will be used. If a topic is selected, the rewritten routing rules will be used.

New routing rule: in the case of selecting a topic, priority should be given to finding the themes / topic name / views / areas / area name / controller name / view name.cshtml. If not, the default routing rule should be followed, that is, areas / area name / views / controller name / view name.cshtml

Switch theme View Code:

<div>
     <button type="button" data-toggle="dropdown">
      <i></i>&nbsp;
      <span>Switch Themes & nbsp</ span>&nbsp;
      <i></i>
     </button>
     <ul role="menu">
      <li>
       <a href="javascript:setTheme('default')">
        <i> < / I > Default Theme
       </a>
      </li>
      <li>
       <a href="javascript:setTheme('Blue')">
        <i> < / I > Blue Theme
       </a>
      </li>
     </ul>
    </div>
  <script type="text/javascript">
   function setTheme(themeName)
   {
    window.location.href = "/Home/SetTheme?themeName=" + themeName + "&href=" + window.location.href;
   }
</script>

When the user logs in successfully, the selected topic information will be read from the cookie. When the topic record is not read from the cookie, the configured topic name will be read from the web.config configuration file. If none is read, it means the default topic and the original view engine rules will be followed.

In the background management interface, every time I select a topic, I will store the topic name in the cookie, and save it for one year by default, so that when I log in again, I can remember the selected topic information.


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 class:

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;
    }
    //Template style
    _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));
   }
  }
 }
}

The implementation method is too simple. I don’t know how to express it. I’d better write it down so that people in need can check it. I hope I can help you. Due to the introduction of a large variety of related files in the project, the files are relatively large, and the source code cannot be uploaded due to the network speed. Please forgive me!