原文地址:https://www.cnblogs.com/ElderJames/p/Customized-View-Path-And-Theme-Switching-In-AspNetCore.html
- 《ASP.NET Core 中的SEO優化(1):中間件實現服務端靜態化緩存》
- 《ASP.NET Core 中的SEO優化(2):中間件中渲染Razor視圖》
- 《ASP.NET Core 中的SEO優化(3):自定義路由匹配和生成》
0|1背景
切換主題,是博客、CMS等系統的必備功能,一般來說,有三種切換主題的需求。
- 在管理后台上傳主題包,並選擇主題
- 前端自動按照頻道、欄目等切換模版
- 用戶在前端切換主題,並記錄用戶的選擇
這三種需求,其實核心原理都是一樣,就是制定一套主題的目錄,切換主題等於切換目錄名。主題內的頁面模版都是按照一定的規則存放的。
下面是兩個主題包的目錄示例:
.
├── theme0
| ├── Assets
| | ├── js
| | ├── css
| | └── img
| ├── Home
| | ├── Index.cshtml
| | └── About.cshtml
| ├── Article
| | ├── Index.cshtml
| | └── Detail.cshtml
| └── Shared
| ├── Page.cshtml
| └── _Layout.cshtml
└── theme1
├── Assets
| ├── js
| ├── css
| └── img
├── Home
| ├── Index.cshtml
| └── About.cshtml
├── Article
| ├── Index.cshtml
| └── Detail.cshtml
└── Shared
├── Page.cshtml
└── _Layout.cshtml
大家一定注意到了,上面每個主題包里都按照傳統ASP.NET MVC的約定來划分目錄:控制器名為文件夾,操作名為視圖文件。其實這里只是方便起見,按照接下來介紹的方法,是可以完全地自定義這個目錄划分的。
0|1原理
當ASP.NET MVC從控制器處理完數據返回視圖的時候,ASP.NET MVC會按照默認的多個路徑去查找文件,如果文件存在,則使用該文件渲染,如果不存在,則尋找下一個路徑,比如默認的路徑會有/{Area}/{Controller}/{Action}.cshtml、/{Controller}/{Action}.cshtml、/Shared/{Action}.cshtml等等我們熟悉的約定,那么在查找視圖文件時,會安裝從左往右的路徑去查詢,如果都查詢不出來,是會報錯的。
而如果要做到切換主題文件夾名來切換主題,我們就需要在默認規則上加主題的目錄占位符,使的查詢時用主題文件夾名來替換占位符,例如/{theme}/{Controller}/{Action}.cshtml、/{theme}/Shared/{Action}.cshtml等等,這樣,當查詢視圖文件時,就能匹配到對應的主題文件夾,並且找到相應的視圖了。
總結起來,切換主題功能有兩個重點需要我們去實現:
- 在原有規則中加入占位符
- 每次請求都獲取當前的主題名,並改變視圖查詢路徑
0|1實現
最簡單的實現,在操作(action)的最后return View(viewPath)時傳入視圖路徑,直接就能指向對應視圖,但是,這樣做一點都不靈活,而且每個操作都要傳路徑也是不夠簡潔,不容易維護,所以我們需要更好的解決方案。
ASP.NET MVC 實現
在ASP.NET MVC時代,我們可通過繼承RazorViewEngine類,在基類的ViewLocationFormats和PartialViewLocationFormats兩個屬性中加入有主題目錄名占位符的路徑,並重寫CreateView、CreatePartialView、FileExists三個方法,使每次請求都能獲取最新的主題名,如下面的例子中從路由數據對象中獲取主題名:
public class TemplateViewEngine : RazorViewEngine { public TemplateViewEngine() : base() { ViewLocationFormats = new[] { "~/Views/{1}%1/{0}.cshtml", "~/Views/{1}/{0}.cshtml",//默認路徑 "~/Views/Shared%1/{0}.cshtml", "~/Views/Shared/{0}.cshtml", }; PartialViewLocationFormats = new[] { "~/Views/{1}%1/{0}.cshtml", "~/Views/{1}/{0}.cshtml",//默認路徑 "~/Views/Shared%1/{0}.cshtml", "~/Views/Shared/{0}.cshtml", }; } protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath) { var template = controllerContext.RouteData.Values["template"] != null ? "/" + controllerContext.RouteData.Values["template"].ToString() : ""; return base.CreatePartialView(controllerContext, partialPath.Replace("%1", template)); } protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath) { var template = controllerContext.RouteData.Values["template"] != null ? "/" + controllerContext.RouteData.Values["template"].ToString() : ""; return base.CreateView(controllerContext, viewPath.Replace("%1", template), masterPath); } protected override bool FileExists(ControllerContext controllerContext, string virtualPath) { var template = controllerContext.RouteData.Values["template"] != null ? "/" + controllerContext.RouteData.Values["template"].ToString() : ""; return base.FileExists(controllerContext, virtualPath.Replace("%1", template)); } }
事實上,如果是需要實現不同用戶不同主題的功能,主題信息可以存儲在Session中,還能從controllerContext實例獲取Session中存儲的主題名。
那么,在ASP.NET Core中如何實現呢?
ASP.NET Core 實現
ASP.NET Core 相比ASP.NET MVC框架,雖然使用上為了開發者平滑過渡,很多約定都相同,但是架構本身是做了翻天覆地的重構和優化,得益於一脈相承的MSDI框架,ASP.NET Core框架實現了組件化,很多功能都通過IoC的方式修改或擴展。例如本文介紹的主題情況功能,就是實現IViewLocationExpander接口來達到擴展配置的目的,而且還比ASP.NET MVC的更加簡潔:
public class TemplateViewLocationExpander : IViewLocationExpander { public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations) { var template = context.Values["template"] ?? "Default"; string[] locations = { "/Views/" + template + "/{1}/{0}.cshtml", "/Views/" + template + "/{0}.cshtml", "/Views/" + template + "/Shared/{0}.cshtml" }; return locations.Union(viewLocations); } public void PopulateValues(ViewLocationExpanderContext context) { context.Values["template"] = context.ActionContext.RouteData.Values["Template"]?.ToString() ?? "Default"; } }
這個接口里面,PopulateValues方法主要用來獲取實時的主題信息,context.ActionContext中除了RouteData可獲得實時數據,還有HttpContext實例可獲得用戶信息,甚至能利用RequestServices實例注入服務。而只有在PopulateValues中修改了context,ExpandViewLocations方法才會從context中獲得主題信息,從而達到修改視圖查找路徑的目的。
當我們實現了IViewLocationExpander接口后,還需要在Startup類的services.AddMvc();下修改MVC的配置:
services.AddMvc();
//配置模版視圖路徑 services.Configure<RazorViewEngineOptions>(options => { options.ViewLocationExpanders.Add(new TemplateViewLocationExpander()); });
PS:這種修改MVC內部配置的方式很有趣,以后有空會研究一番。
0|1總結
本文主要介紹了在ASP.NET Core中利用修改視圖查詢路徑實現主題切換的功能,雖然只介紹了核心部分,但是其它部分如管理主題、前端切換等功能,都是很容易實現的,以后我會在我的框架樣例中實現,敬請大家關注啦
在開發MVC的過程中可能遇到這種情況:我希望我的視圖可以放在自定義的文件夾下,而不是放在默認的Views文件夾下,這時我就需要更改MVC的默認路徑
如圖,我的移動端和PC端是兩套單獨設計的頁面,所以我把視圖拆分到了兩個文件夾下:PC和Mobile,但是MVC的默認路徑是找不到這兩個文件夾的,所以我進行了以下更改

ASP.NET Core中有一個接口IViewLocationExpander 通過繼承這個接口我們可以更改,MVC訪問的默認文件路徑

接口中的兩個方法PopulateValues 方法可以讓我在ViewLocationExpanderContext上下文中添加后續可能會用到的鍵值對
ExpandViewLocations 方法會在MVC無法找到默認的視圖路徑時調用,動態的返回需要的路徑
通過這種方法,我們就可以實現更改MVC的視圖默認訪問路徑了
————————————————
版權聲明:本文為CSDN博主「_Tassdar」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/CuiLanren/article/details/81566504
