ASP.NET Core 中的SEO優化(4):自定義視圖路徑及主題切換


系列回顧

背景

切換主題,是博客、CMS等系統的必備功能,一般來說,有三種切換主題的需求。

  1. 在管理后台上傳主題包,並選擇主題
  2. 前端自動按照頻道、欄目等切換模版
  3. 用戶在前端切換主題,並記錄用戶的選擇

這三種需求,其實核心原理都是一樣,就是制定一套主題的目錄,切換主題等於切換目錄名。主題內的頁面模版都是按照一定的規則存放的。

下面是兩個主題包的目錄示例:

  .
  ├── 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的約定來划分目錄:控制器名為文件夾,操作名為視圖文件。其實這里只是方便起見,按照接下來介紹的方法,是可以完全地自定義這個目錄划分的。

原理

當ASP.NET MVC從控制器處理完數據返回視圖的時候,ASP.NET MVC會按照默認的多個路徑去查找文件,如果文件存在,則使用該文件渲染,如果不存在,則尋找下一個路徑,比如默認的路徑會有/{Area}/{Controller}/{Action}.cshtml/{Controller}/{Action}.cshtml/Shared/{Action}.cshtml等等我們熟悉的約定,那么在查找視圖文件時,會安裝從左往右的路徑去查詢,如果都查詢不出來,是會報錯的。

而如果要做到切換主題文件夾名來切換主題,我們就需要在默認規則上加主題的目錄占位符,使的查詢時用主題文件夾名來替換占位符,例如/{theme}/{Controller}/{Action}.cshtml/{theme}/Shared/{Action}.cshtml等等,這樣,當查詢視圖文件時,就能匹配到對應的主題文件夾,並且找到相應的視圖了。

總結起來,切換主題功能有兩個重點需要我們去實現:

  1. 在原有規則中加入占位符
  2. 每次請求都獲取當前的主題名,並改變視圖查詢路徑

實現

最簡單的實現,在操作(action)的最后return View(viewPath)時傳入視圖路徑,直接就能指向對應視圖,但是,這樣做一點都不靈活,而且每個操作都要傳路徑也是不夠簡潔,不容易維護,所以我們需要更好的解決方案。

ASP.NET MVC 實現

在ASP.NET MVC時代,我們可通過繼承RazorViewEngine類,在基類的ViewLocationFormatsPartialViewLocationFormats兩個屬性中加入有主題目錄名占位符的路徑,並重寫CreateViewCreatePartialViewFileExists三個方法,使每次請求都能獲取最新的主題名,如下面的例子中從路由數據對象中獲取主題名:

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中修改了contextExpandViewLocations方法才會從context中獲得主題信息,從而達到修改視圖查找路徑的目的。

當我們實現了IViewLocationExpander接口后,還需要在Startup類的services.AddMvc();下修改MVC的配置:

services.AddMvc();
//配置模版視圖路徑
services.Configure<RazorViewEngineOptions>(options =>
{
    options.ViewLocationExpanders.Add(new TemplateViewLocationExpander());
});

PS:這種修改MVC內部配置的方式很有趣,以后有空會研究一番。

總結

本文主要介紹了在ASP.NET Core中利用修改視圖查詢路徑實現主題切換的功能,雖然只介紹了核心部分,但是其它部分如管理主題、前端切換等功能,都是很容易實現的,以后我會在我的框架樣例中實現,敬請大家關注啦!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM