之前有寫過一篇在 IIS 用 URL Rewrite 解決 SEO 要求網址全小寫及有無斜線結尾的問題,到了 ASP.NET Core 雖然沒有 URL Rewrite 擴充套件可以安裝,但是有一個 URL Rewriting Middleware 可以來幫助我們做到一樣的事情。
原本這件事情我是想要在 Nginx 直接用設定的方式解決,但是找到的解法都是需要簽出 Nginx 的原始碼,透過新增插件、調整編譯參數的方式重新編譯 Nginx,這就讓事情變得複雜了,所以轉而使用 URL Rewriting Middleware。
基本用法
於 Startup.cs
的 Configure()
方法裡面,在我們想要的位置插入 app.UseRewriter(...)
程式碼,底下是一個強制將 http 重新導向到 https 的規則。
上面這個只是其中一個已經內建的規則,最重要的是我們可以寫正則表達式來建立我們想要的規則,底下是一個將 home/*.aspx 網址,做 301 轉址到 home/* 的規則。
/
根路徑符號的,所以我們如果這樣寫 AddRedirect("/home/(.*)\\.aspx", "/home/${1}", 301)
是不會匹配到的,如果有需要強調是 home 開頭,則改寫為 AddRedirect("^home/(.*)\\.aspx", "home/${1}", 301)
除非匹配邏輯是我們自己寫的,就另當別論了。美中不足
正當以為掌握了寫 URL Rewriting 規則的方法之後,將網址大寫轉小寫只是蛋糕一塊的時候,發現它並沒有那麼簡單,原因在於 AddRedirect()
擴充方法是使用 Match.Result() 這個方法來替換匹配字元,而 replacement 的語法裡面沒有支援將匹配字元轉成大寫或小寫的語法,只好依樣畫葫蘆,參考 RedirectRule.cs 原始碼新增一個 RedirectLowercaseRule
。
internal sealed class RedirectLowercaseRule : IRule
{
public void ApplyRule(RewriteContext context)
{
var path = context.HttpContext.Request.Path == PathString.Empty
? context.HttpContext.Request.Path.ToString()
: context.HttpContext.Request.Path.ToString().Substring(1);
if (!Uri.UnescapeDataString(path).Any(char.IsUpper)) return;
var pathBase = context.HttpContext.Request.PathBase;
// 強制大寫轉小寫
var newPath = Regex.Replace(path, "[A-Z]", m => m.Value.ToLower());
var response = context.HttpContext.Response;
// 永久轉址
response.StatusCode = 301;
context.Result = RuleResult.EndResponse;
if (string.IsNullOrEmpty(newPath))
{
response.Headers[HeaderNames.Location] = pathBase.HasValue ? pathBase.Value : "/";
return;
}
if (newPath.IndexOf("://", StringComparison.Ordinal) == -1 && newPath[0] != '/')
{
newPath = string.Concat("/", newPath);
}
var split = newPath.IndexOf('?');
if (split >= 0)
{
var query = context.HttpContext.Request.QueryString.Add(QueryString.FromUriComponent(newPath.Substring(split)));
response.Headers[HeaderNames.Location] = string.Concat(pathBase, newPath.Substring(0, split), query.ToUriComponent());
}
else
{
response.Headers[HeaderNames.Location] = string.Concat(pathBase, newPath, context.HttpContext.Request.QueryString.ToUriComponent());
}
}
}
接著,為了方便操作,新增一個 RewriteOptions
的擴充方法 AddRedirectToLowercasePermanent()
。
public static class RewriteOptionsExtension
{
public static RewriteOptions AddRedirectToLowercasePermanent(this RewriteOptions options)
{
options.Rules.Add(new RedirectLowercaseRule());
return options;
}
}
最後,加上網址無斜線結尾的規則,就大功告成了。
Rewrite
與 Redirect
的差別在於,Rewrite 是伺服器內部的網址轉換行為,網址經過轉換之後,實際上 Route 到的是另一個服務,而 Client 端所看到的網址不會變,但是呈現的會是另一個服務的內容;Redirect 是 Client 端的轉址行為,Client 的網址會變,呈現的會是另一個網址的內容。