構建 URL 重寫引擎
為了有助於描述如何在 ASP.NET Web 應用程式中實現 URL 重寫,我建立了 URL 重寫引擎。此重寫引擎將提供以下功能:
使用 URL 重寫引擎的 ASP.NET 頁面開發人員可以在 Web.config 檔案中指定重寫規則。
重寫規則可以使用Regex來實現功能強大的重寫規則。
可以輕鬆地將 URL 重寫配置為使用 HTTP 模組或 HTTP 處理常式。
在本文中,我們將介紹僅使用 HTTP 模組的 URL 重寫。要查看如何使用 HTTP 處理常式來執行 URL 重寫,請參考可隨本文下載的代碼。
為 URL 重寫引擎指定配置資訊
讓我們先介紹一下 Web.config 檔案中重寫規則的結構。首先,您需要在 Web.config 檔案中指明要使用 HTTP 模組還是 HTTP 處理常式來執行 URL 重寫。在下載代碼中,Web.config 檔案包含兩個已注釋掉的條目:
<!--<httpModules> <add type="URLRewriter.ModuleRewriter, URLRewriter" name="ModuleRewriter" /></httpModules>--><!--<httpHandlers> <add verb="*" path="*.aspx" type="URLRewriter.RewriterFactoryHandler, URLRewriter" /></httpHandlers>-->
注釋掉 <httpModules> 條目,以使用 HTTP 模組執行重寫;注釋掉 <httpHandlers> 條目,以使用 HTTP 處理常式執行重寫。
除了指定使用 HTTP 模組還是 HTTP 處理常式執行重寫外,Web.config 檔案還包含重寫規則:重寫規則由兩個字串組成:要在被請求的 URL 中尋找的模式;要替換此模式的字串(如果找到)。在 Web.config 檔案中,此資訊是使用以下文法表達的:
<RewriterConfig> <Rules> <RewriterRule> <LookFor>要尋找的模式</LookFor> <SendTo>要用來替換模式的字串</SendTo> </RewriterRule> <RewriterRule> <LookFor>要尋找的模式</LookFor> <SendTo>要用來替換模式的字串</SendTo> </RewriterRule> ... </Rules></RewriterConfig>
每個重寫規則均由 <RewriterRule> 元素表達。要搜尋的模式由 <LookFor> 元素指定,而要替換所找到的模式的字串將在 <SentTo> 元素中輸入。這些重寫規則將從頭到尾進行計算。如果發現與某個規則匹配,URL 將被重寫,並且對重寫規則的搜尋將會終止。
在 <LookFor> 元素中指定模式時,請注意,要使用Regex來執行匹配和字串替換。(稍後,我們將介紹一個真實的樣本,說明如何使用Regex來搜尋模式。)由於模式是Regex,應確保轉義Regex中的任何保留字元。(一些Regex保留字元包括:.、?、^、$ 及其他。可以通過在前面加反斜線(如 /.)對這些字元進行轉義,以匹配文字句點。)
使用 HTTP 模組執行 URL 重寫
建立 HTTP 模組與建立可以實現 IHttpModule 介面的類一樣簡單。IHttpModule 介面定義了兩種方法:
為了便於為 URL 重寫建立 HTTP 模組,我將從建立抽象基類 BaseModuleRewriter 開始介紹。此類將實現 IHttpModule。在 Init() 事件中,它將 HttpApplication 的 AuthorizeRequest 事件綁定到 BaseModuleRewriter_AuthorizeRequest 方法。BaseModuleRewriter_AuthorizeRequest 方法將調用該類傳入被請求的 Path 的 Rewrite() 方法,以及傳入 Init() 方法的 HttpApplication 對象。Rewrite() 方法是抽象的,也就是說,在 BaseModuleRewriter 類中,Rewrite() 方法沒有方法主體;從 BaseModuleRewriter 派生而來的類必須覆蓋此方法並提供方法主體。
具有此基類後,只需建立由 BaseModuleRewriter 派生的類即可,該類可以覆蓋 Rewrite() 並在那裡執行 URL 重寫邏輯。下面顯示了 BaseModuleRewriter 的代碼。
public abstract class BaseModuleRewriter : IHttpModule{ public virtual void Init(HttpApplication app) { // 警告!此代碼不適用於 Windows 身分識別驗證! // 如果使用 Windows 身分識別驗證, // 請改為 app.BeginRequest app.AuthorizeRequest += new EventHandler(this.BaseModuleRewriter_AuthorizeRequest); } public virtual void Dispose() {} protected virtual void BaseModuleRewriter_AuthorizeRequest( object sender, EventArgs e) { HttpApplication app = (HttpApplication) sender; Rewrite(app.Request.Path, app); } protected abstract void Rewrite(string requestedPath, HttpApplication app);}
請注意,BaseModuleRewriter 類將在 AuthorizeRequest 事件中執行 URL 重寫。如上所述,如果將 Windows 身分識別驗證與檔案授權結合使用,您需要對此做出更改,以便可以在 BeginRequest 或 AuthenticateRequest 事件中執行 URL 重寫。
ModuleRewriter 類擴充了 BaseModuleRewriter 類,並負責執行實際的 URL 重寫。ModuleRewriter 包含單一覆蓋方法(Rewrite()),如下所示:
protected override void Rewrite(string requestedPath, System.Web.HttpApplication app){ // 獲得配置規則 RewriterRuleCollection rules = RewriterConfiguration.GetConfig().Rules; // 遍曆每個規則... for(int i = 0; i < rules.Count; i++) { // 獲得要尋找的模式,並且 // 解析 Url(轉換為相應的目錄) string lookFor = "^" + RewriterUtils.ResolveUrl(app.Context.Request.ApplicationPath, rules[i].LookFor) + "$"; // 建立 regex(請注意,已設定 IgnoreCase...) Regex re = new Regex(lookFor, RegexOptions.IgnoreCase); // 查看是否找到了匹配的規則 if (re.IsMatch(requestedPath)) { // 找到了匹配的規則 -- 進行必要的替換 string sendToUrl = RewriterUtils.ResolveUrl(app.Context.Request.ApplicationPath, re.Replace(requestedPath, rules[i].SendTo)); // 重寫 URL RewriterUtils.RewriteUrl(app.Context, sendToUrl); break; // 退出 For 迴圈 } }}
Rewrite() 方法從擷取 Web.config 檔案中的一組重寫規則開始。然後,它將遍曆重寫規則,每次遍曆一個,對於每個規則,它將擷取規則的 LookFor 屬性,並使用Regex來確定是否在被請求的 URL 中找到了匹配的規則。
如果找到了匹配的規則,將在具有 SendTo 屬性值的被請求路徑上執行Regex替換。然後,替換後的 URL 將被傳遞到 RewriterUtils.RewriteUrl() 方法中。RewriterUtils 是一個 helper 類,此類將提供一對由 URL 重寫 HTTP 模組和 HTTP 處理常式使用的靜態方法。RewriterUrl() 方法僅調用 HttpContext 對象的 RewriteUrl() 方法。
注意:您可能已注意到,執行Regex匹配和替換時,將調用 RewriterUtils.ResolveUrl()。此 helper 方法只替換具有應用程式路徑值的字串中的所有 ~ 執行個體。
URL 重寫引擎的整個代碼可隨本文下載。我們已經介紹了大部分密切相關的組件,但還有一些其他組件(例如,對 Web.config 檔案中 XML 格式的重寫規則進行還原序列化以使其成為對象的類),以及用於 URL 重寫的 HTTP 處理常式工廠。本文剩餘的三個部分將對 URL 重寫的實際使用方式進行介紹。
返回頁首 使用 URL 重寫引擎執行簡單的 URL 重寫
為了實際示範 URL 重寫引擎,我們來構建一個使用簡單 URL 重寫的 ASP.NET Web 應用程式。假設我們所工作的公司通過網路銷售分類產品。這些產品分為以下幾個類別:
類別 ID |
類別名稱 |
1 |
飲料 |
2 |
調味品 |
3 |
糖果 |
4 |
奶製品 |
... |
... |
假設我們已建立了名為 ListProductsByCategory.aspx 的 ASP.NET 網頁,該網頁在查詢字串中接受類別 ID 值,並顯示屬於該類的所有產品。因此,要查看我們銷售的飲料的使用者可以訪問 ListProductsByCategory.aspx?CategoryID=1,而那些要查看奶製品的使用者可以訪問 ListProductsByCategory.aspx?CategoryID=4。此外,還假設我們有一個名為 ListCategories.aspx 的頁面,該頁面列出了待售的所有產品類別。
很顯然,這是一個 URL 重寫案例,因為提供給使用者的 URL 沒有為使用者帶來任何意義,也沒有為他們提供任何“可刪節性”。因此,讓我們使用 URL 重寫,以便在使用者訪問 /Products/Beverages.aspx 時,他們的 URL 將被重寫為 ListProductsByCategory.aspx?CategoryID=1。我們可以在 Web.config 檔案中使用以下 URL 重寫規則來實現此功能。
<RewriterConfig> <Rules> <!-- 產品製表者規則 --> <RewriterRule> <LookFor>~/Products/Beverages/.aspx</LookFor> <SendTo>~/ListProductsByCategory.aspx?CategoryID=1</SendTo> </RewriterRule> <RewriterRule> </Rules></RewriterConfig>
正如您可以看到的,此規則將進行搜尋,以查看使用者請求的路徑是否為 /Products/Beverages.aspx。如果是,它便將 URL 重寫為 /ListProductsByCategory.aspx?CategoryID=1。
注意:請注意,<LookFor> 元素對 Beverages.aspx 中的句點進行了轉義。這是因為在Regex模式中使用了 <LookFor> 值,並且句點是Regex中的特殊字元,該字元表示“匹配任一字元”,例如,與 URL /Products/BeveragesQaspx 匹配。通過轉義句點(使用 /.),可以表明我們要匹配的是文字句點,而不是任何舊的字元。
有了此規則之後,當使用者訪問 /Products/Beverages.aspx 時,頁面上將顯示待售的飲料。圖 3 顯示了訪問 /Products/Beverages.aspx 的瀏覽器的快照。請注意,在瀏覽器的地址欄中,URL 將讀取 /Products/Beverages.aspx,但使用者實際看到的是 ListProductsByCategory.aspx?CategoryID=1 的內容。(實際上,Web 服務器上根本不存在 /Products/Beverages.aspx 檔案!)
圖 3. 重寫 URL 之後請求類別
與 /Products/Beverages.aspx 相似,下面我們要為其他產品類別添加重寫規則。此操作僅包括在 Web.config 檔案的 <Rules> 元素內添加附加的 <RewriterRule> 元素。請參閱下載內容中的 Web.config 檔案,以擷取用於此示範的一組完整的重寫規則。
為了使 URL 更具可刪節性,最好使使用者只需從 /Products/Beverages.aspx 中刪除 Beverages.aspx 即可看到產品類別的列表。乍一看,這可能是一項很普通的任務(只需添加一個將 /Products/ 映射到 /ListCategories.aspx 的重寫規則即可)。但此操作存在一個微妙之處,即您必須首先建立一個 /Products/ 目錄,並在 /Products/ 目錄中添加一個空的 Default.aspx 檔案。
要理解需要執行這些額外步驟的原因,可以參考前面的內容,即 URL 重寫引擎位於 ASP.NET 層級上。也就是說,如果 ASP.NET 引擎永遠沒有機會處理請求,URL 重寫引擎就沒有辦法檢測傳入的 URL。而且,請記住,僅當被請求的檔案具有相應的副檔名時,IIS 才會將傳入請求傳遞給 ASP.NET 引擎。因此,如果使用者訪問 /Products/,而 IIS 沒有看到任何副檔名,那麼它將檢查目錄,以查看是否存在這樣一個檔案,即該檔案名稱為預設檔案名稱中的一個。(Default.aspx、Default.htm、Default.asp 等等。“IIS 管理”對話方塊中“Web 服務器屬性”對話方塊的“文檔”選項卡對這些預設檔案名稱進行了定義。)當然,如果 /Products/ 目錄不存在,IIS 將返回 HTTP 404 錯誤。
因此,我們需要建立 /Products/ 目錄。另外,我們還需要在此目錄中建立一個檔案 Default.aspx。這樣,當使用者訪問 /Products/ 時,IIS 將檢測目錄,查看是否存在一個名為 Default.aspx 的檔案,然後將處理過程傳遞給 ASP.NET 引擎。然後,URL 重寫器將在重寫 URL 時分解。
建立目錄和 Default.aspx 檔案後,請繼續操作,並向 <Rules> 元素中添加以下重寫規則:
<RewriterRule> <LookFor>~/Products/Default/.aspx</LookFor> <SendTo>~/ListCategories.aspx</SendTo></RewriterRule>
有了此規則之後,當使用者訪問 /Products/ 或 /Products/Default.aspx 時,他們將看到產品類別列表, 4 所示。
圖
4. 向 URL 添加“可刪節性”處理回傳
如果要重寫的 URL 中包含一個伺服器端的 Web Form並執行回傳,則表單回傳後,將使用帶底線的 URL。也就是說,如果使用者在瀏覽器中輸入 /Products/Beverages.aspx,他們在瀏覽器地址欄中看到的將是 /Products/Beverages.aspx,但是他們看到的內容將是 ListProductsByCategory.aspx?CategoryID=1 的內容。如果 ListProductsByCategory.aspx 執行了回傳,使用者將被回傳到 ListProductsByCategory.aspx?CategoryID=1,而不是 /Products/Beverages.aspx。這樣不會中斷任何內容,但從使用者的角度考慮,如果單擊按鈕時突然看到 URL 更改會使他們感到不安。
出現這種情況的原因是:在呈現 Web Form時,它會將其操作屬性直接設定為 Request 對象中檔案路徑的值。當然,在呈現 Web Form時,URL 已從 /Products/Beverages.aspx 重寫為 ListProductsByCategory.aspx?CategoryID=1,這表明 Request 對象報告使用者要訪問 ListProductsByCategory.aspx?CategoryID=1。只需使伺服器端表單不呈現操作屬性即可解決此問題。(預設情況下,如果表單不包含操作屬性,瀏覽器將會回傳。)
不幸的是,Web Form不允許您明確指定操作屬性,也不允許您設定某些屬性以禁用操作屬性的呈現。因此,我們必須自己來擴充 System.Web.HtmlControls.HtmlForm 類,覆蓋 RenderAttribute() 方法並明確指出它不會呈現操作屬性。
由於繼承功能,我們可以獲得 HtmlForm 類的所有功能,並且只需添加幾行代碼即可獲得所需的行為。以下顯示了自訂類的完整代碼:
namespace ActionlessForm { public class Form : System.Web.UI.HtmlControls.HtmlForm { protected override void RenderAttributes(HtmlTextWriter writer) { writer.WriteAttribute("name", this.Name); base.Attributes.Remove("name"); writer.WriteAttribute("method", this.Method); base.Attributes.Remove("method"); this.Attributes.Render(writer); base.Attributes.Remove("action"); if (base.ID != null) writer.WriteAttribute("id", base.ClientID); } }}
已被覆蓋的 RenderAttributes() 方法的代碼僅包含 HtmlForm 類的 RenderAttributes() 方法的準確代碼,而不設定作業屬性。(我使用 Lutz Roeder 的 {
Track('ctl00_MTCS_main_ctl00|ctl00_MTCS_main_ctl12',this);
}" href="http://www.aisto.com/roeder/DotNet/">Reflector 來查看 HtmlForm 類的原始碼。)
建立此類並對其進行編譯之後,要在 ASP.NET Web 應用程式中使用它,應首先將其添加到 Web 應用程式的 References 檔案夾中。然後,要使用它來代替 HtmlForm 類,只需在 ASP.NET 網頁的頂部添加以下內容即可:
<%@ Register TagPrefix="skm" Namespace="ActionlessForm" Assembly="ActionlessForm" %>
然後,將 <form runat="server">(如果有)替換為:
<skm:Form id="Form1" method="post" runat="server">
並將右邊的 </form> 標記替換為:
</skm:Form>
您可以在 ListProductsByCategory.aspx(包含在本文的下載代碼中)中發現操作中的此自訂 Web Form 類。下載內容中還包含了用於無操作 Web Form 的 Visual Studio .NET 項目。
注意:如果要重寫的目標 URL 沒有執行回傳,則無需使用此自訂 Web Form 類。
返回頁首 建立真正“可刪節”的 URL
前一部分中介紹的簡單 URL 重寫顯示了如何輕鬆地為 URL 重寫引擎配置新的重寫規則。但在使用Regex時,重寫規則的真正功能才會發揮更大作用,本部分將對此進行探討。
Blog 在當今正變得越來越流行,似乎每個人都擁有自己的 blog。如果您不熟悉 blog:blog 是經常更新的個人頁面,通常作為聯機期刊。大多數 blog 只記錄每天發生的事情,還有一些 blog 可能關注於特定的主題(例如,電影回顧、體育團隊或電腦技術)。
可以在任何地點對 blog 進行更新,更新頻率為從每天幾次到每周一次或兩次,具體情況取決於作者。通常,blog 首頁將顯示最近的 10 個條目,但實際上,所有 blog 軟體均提供存檔,訪問者可以通過存檔讀取較早的文章。Blog 是用於“可刪節”URL 的一個功能強大的應用程式。假設在搜尋 blog 的存檔時,您在 URL /2004/02/14.aspx 上發現了您自己。如果您發現自己在閱讀 2004 年 2 月 14 日的文章,您是否覺得很驚訝?而且,您可能希望查看 2004 年 2 月的所有文章,在這種情況下,您可以嘗試將 URL 刪節為 /2004/02/。要查看 2004 年的所有文章,您可以嘗試訪問 /2004/。
維護 blog 時,最好為訪問者提供此層級的 URL“可刪節性”。許多 blog 引擎都提供此功能,但我們將討論如何使用 URL 重寫來實現此功能。
首先,我們需要一個 ASP.NET 網頁,此頁面將按照日、月或年來顯示 blog 條目。假設我們有一個 ShowBlogContent.aspx 頁面,該頁面的查詢字串參數為年、月和日。要查看 2004 年 2 月 14 日的文章,我們可以訪問 ShowBlogContent.aspx?year=2004&month=2&day=14。要查看 2004 年 2 月的所有文章,我們可以訪問 ShowBlogContent.aspx?year=2004&month=2。最後,要查看 2004 年的所有文章,我們可以瀏覽到 ShowBlogContent.aspx?year=2004。(可以在本文的下載內容中找到 ShowBlogContent.aspx 的代碼。)
在這種情況下,如果使用者訪問 /2004/02/14.aspx,我們需要將 URL 重寫為 ShowBlogContent.aspx?year=2004&month=2&day=14。所有三種情況(URL 指定了年、月和日時;URL 僅指定了年和月時;URL 僅指定了年時)均可使用重寫規則進行處理:
<RewriterConfig> <Rules> <!-- Blog 內容顯示程式規則 --> <RewriterRule> <LookFor>~/(/d{4})/(/d{2})/(/d{2})/.aspx</LookFor> <SendTo>~/ShowBlogContent.aspx?year=$1&month=$2&day=$3</SendTo> </RewriterRule> <RewriterRule> <LookFor>~/(/d{4})/(/d{2})/Default/.aspx</LookFor> <SendTo><![CDATA[~/ShowBlogContent.aspx?year=$1&month=$2]]></SendTo> </RewriterRule> <RewriterRule> <LookFor>~/(/d{4})/Default/.aspx</LookFor> <SendTo>~/ShowBlogContent.aspx?year=$1</SendTo> </RewriterRule> </Rules></RewriterConfig>
這些重寫規則表明了Regex的功能。在第一個規則中,我們使用模式 (/d{4})/(/d{2})/(/d{2})/.aspx 尋找 URL。在簡明英語中,它對應了這樣一個字串:首先是四個數字,後跟一個斜杠,然後是兩個數字,後跟一個斜杠,然後再跟兩個數字,最後是一個 .aspx。每個數字組周圍的括弧非常重要,通過它可以在相應的 <SendTo> 屬性中引用這些括弧內的匹配字元。 特別是,我們可以針對第一、第二和第三個括弧組分別使用 $1、$2 和 $3 引用回括弧內的匹配組。
注意:由於 Web.config 檔案採用 XML 格式,但是必須對元素文字部分中的字元(如 &、< 和 >)進行轉義。在第一個規則的 <SendTo> 元素中,& 被轉義為 &。在第二個規則的 <SendTo> 中使用了另外一種技術(使用 <![CDATA[...]]> 元素),無需對內部的內容進行轉義。可以使用兩種方法中的任何一種,並且都會得到相同的結果。
圖 5、6 和 7 顯示了操作中的 URL 重寫。資料實際上是從我的 blog {
Track('ctl00_MTCS_main_ctl00|ctl00_MTCS_main_ctl13',this);
}" href="http://scottonwriting.net/">http://scottonwriting.net/ 中拖過來的。圖 5 中顯示了 2003 年 11 月 7 日的文章;圖 6 中顯示了 2003 年 11 月的所有文章;圖 7 顯示了 2003 年的所有文章。
圖
5. 2003 年 11 月 7 日的文章
圖
6. 2003 年 11 月的所有文章
圖
7. 2003 年的所有文章
注意:URL 重寫引擎在 <LookFor> 元素中需要使用Regex模式。如果您對Regex不熟悉,可以閱讀我在早些時候編寫的一篇文章 {
Track('ctl00_MTCS_main_ctl00|ctl00_MTCS_main_ctl14',this);
}" href="http://www.4guysfromrolla.com/webtech/090199-1.shtml">An Introduction to Regular Expressions。另外,還有一個很好的網站:{
Track('ctl00_MTCS_main_ctl00|ctl00_MTCS_main_ctl15',this);
}" href="http://regexlib.com/">RegExLib.com,在那裡您可以擷取有關常用Regex的協助資訊,還可以共用您自己的自訂Regex。
構建必備的目錄結構
當請求 /2004/03/19.aspx 時,IIS 將通知 .aspx 擴充,並將請求路由到 ASP.NET 引擎。請求在 ASP.NET 引擎的管道中移動時,URL 將被重寫為 ShowBlogContent.aspx?year=2004&month=03&day=19,並且訪問者會看到 2004 年 3 月 19 日的 blog 條目。但是當使用者瀏覽到 /2004/03/ 時將會發生什麼情況呢?除非有一個 /2004/03/ 目錄,否則 IIS 將返回一個 404 錯誤。此外,此目錄中還需要具有 Default.aspx 頁面,以便可以將請求傳遞給 ASP.NET 引擎。
因此,要使用這種方法,必須手動建立一個用於每年的目錄(其中包含 blog 條目),並且目錄中具有一個 Default.aspx 頁面。另外,在每年目錄中,您需要再手動建立十二個目錄(01、02、?、?...、12),並且每個目錄中均有一個 Default.aspx 檔案。(如上所述,我們還必須執行前面示範中的操作,即在 /Products/ 目錄中添加一個 Default.aspx 檔案,以便訪問 /Products/ 時可以正確顯示 ListCategories.aspx。)
很顯然,添加這樣一個目錄結構可能是一件很痛苦的事情。解決此問題的方法是使所有傳入的 IIS 請求都映射到 ASP.NET 引擎。通過這種方法,即使存取 URL /2004/03/,IIS 也會如實地將請求傳遞給 ASP.NET 引擎(即使並不存在 /2004/03/ 目錄)。但是,使用這種方法將使 ASP.NET 引擎負責處理到達 Web 服務器的所有類型的傳入請求,包括映像、CSS 檔案、外部 JavaScript 檔案、Macromedia Flash 檔案,等等。
對處理所有檔案類型的全面討論遠遠超出了本文的範圍。有關使用此技術的 ASP.NET Web 應用程式的樣本,請參閱 .Text,一個開放源 blog 引擎。{
Track('ctl00_MTCS_main_ctl00|ctl00_MTCS_main_ctl16',this);
}" href="http://www.gotdotnet.com/community/workspaces/default.aspx">.Text 可以配置為將所有請求均映射到 ASP.NET 引擎。它可以使用自訂 HTTP 處理常式來處理產生所有檔案類型的問題,自訂 HTTP 處理常式瞭解如何產生典型的靜態檔案類型(映像、CSS 檔案,等等)。
返回頁首 結論
在本文中,我們討論了如何在 ASP.NET 層級通過 HttpContext 類的 RewriteUrl() 方法來執行 URL 重寫。正如我們所看到的,RewriteUrl() 更新了特定的 HttpContext's Request 屬性,從而更新了被請求的檔案和路徑。最終結果是,從使用者角度來看,他們要訪問某個特定的 URL,但從 Web 服務器端來看,被請求的卻是另一個 URL。
可以在 HTTP 模組或 HTTP 處理常式中重寫 URL。在本文中,我們介紹了如何使用 HTTP 模組執行重寫,並討論了在管道中的不同階段執行重寫的結果。
當然,如果執行 ASP.NET 層級的重寫,則僅當已成功地將請求從 IIS 傳遞給 ASP.NET 引擎後才會發生 URL 重寫。實際上,只有使用者請求帶 .aspx 副檔名的頁面時才會出現這種情況。但是,如果您要使使用者可以進入實際並不存在的 URL,但又希望重寫到現有的 ASP.NET 頁面,則必須建立虛擬目錄和 Default.aspx 頁面,或者對 IIS 進行配置,以使所有傳入請求一律被路由到 ASP.NET 引擎。