The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

ASP.NET MVC 開發心得分享 (23):反向表列路由限制的語法

我曾經在【ASP.NET MVC 開發心得分享 (21):Routing 觀念與技巧】這篇文章中分享過幾個路由開發技巧,其中在【技巧 1:替 Routing 網址設立條件限制】的部分有示範如何透過簡單的 RegEx 規則運算式 (正則表達式) 來限制路由變數的內容規則。不過,通常你在網路上能查到的這些 路由限制 (Route Constraints) 範例,大多使用「正向表列」的方式進行比對,這的確在大部分開發情境下都是這樣用的,但在特定比較少見的開發情境下,你或許需要「反向表列」的方式來限定路由參數的比對規則,尤其是在 ASP.NET MVCASP.NET HttpHandler 混合執行的情況下,更容易遇到這���的問題。

執行環境描述:

假設你的 ASP.NET MVC 網站想要新增一個 HttpHandler 在網站裡,這邊我寫了一個範例程式如下:

  • 組件名稱:RouteConstraintsSample
  • 命名空間:RouteConstraintsSample.HttpHandlers
  • 類別名稱:IndexB
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace RouteConstraintsSample.HttpHandlers
{
    public class IndexB : IHttpHandler
    {
        public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType = "text/plain";
            context.Response.Write("Hello World");
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}

這裡我假設因為專案需求的關係,該 HttpHandler 的網址必須呈現以下網址格式:

  • http://xxxxxxxxxx/IndexB

那麼,我的 web.config 必須這樣定義:

<configuration>
  <system.web>
    <httpHandlers>
      <add path="IndexB"
           verb="GET,HEAD,POST"
           type="RouteConstraintsSample.HttpHandlers.IndexB, RouteConstraintsSample"/>
    </httpHandlers>
  </system.web>
  <system.webServer>
    <handlers>
      <add name="IndexB"
           path="IndexB"
           verb="GET,HEAD,POST"
           type="RouteConstraintsSample.HttpHandlers.IndexB, RouteConstraintsSample"/>
    </handlers>
  </system.webServer>
</configuration>

設定完成後,如果你直接執行該網站,並試圖開啟 /IndexB 這個 HttpHandler 處理常式,你將會得到以下錯誤畫面:

然而,會發生這個錯誤,最主要的原因還是來自於 ASP.NET MVC 的路由定義,我們先來看看在 ASP.NET MVC 預設專案範本的路由定義如下,你可以看到,預設第一個 網址段落 (Path Segment) 便是 {controller} 這個路由變數:

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

所以當執行如上圖的 http://localhost:25102/IndexB 位址時,網址路徑出現的第一個 IndexB 自然會被辨識為 {controller} 路由參數,而我們的 ASP.NET MVC 控制器中,又沒有任何名為 IndexB 的控制器類別,所以就引發了一個 HTTP 404 的錯誤。 ( 註: 這個錯誤是 ASP.NET MVC 送出來的 )

 

解決方案:

要解決這種網址路由衝突的情況,最簡單的方式就是透過 路由限制 (Route Constraints) 的定義,來排除那些我們不想要比對到的路由,以本文範例來說,也就是 IndexB 這一個。

如果我們 ASP.NET MVC 專案中只有兩個控制器,分別是 AccountController 與 HomeController 這兩個類別,你可能會把路由定義改成以下這樣。而這也是本文稍早所說的以「正向表列」的方式進行比對

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
    constraints: new { controller = "(Home)|(Account)" }
);

不過,在實務上,一個網站專案,不可能只有兩個控制器類別,而且網站會一直長大,需求會不斷新增,如果一直用「正向表列」的方式定義,也會讓專案的關注點不夠分離而造成一些網站維護上的困擾,因為你每次新增控制器都要手動在 App_Start\RouteConfig.cs 新增一個限制條件,出錯的機率也非常高,尤其是在網站交接的過程中,不容易把這些細節交代清楚。

但是,前陣子就有同事來問我,他們希望能夠用「反向表列」的方式定義路由限制,但怎樣都無法使用 RegEx 寫出來,最後雖然用自訂 Routing 條件限制的方式寫出來 (透過一個自訂類別並實做 IRouteConstraint 介面的方式),但總覺得不死心,問我用 RegEx 真的可以寫出「反向表列」的規則定義嗎?

正所謂江湖一點訣,看得懂 RegEx 規則運算式是一種能夠閱讀天書的能力,但寫得出來才能算是真正有實力,大家都應該好好練練。這話說得不誇張,我在十幾年前玩 Perl 的時候,曾經有一整年的時間經常接觸 RegEx 規則運算式,所以對語法瞭若指掌,當我被問及這個需求時,還真的也想了一些時間才寫出來,今天這篇文章寫得這麼長,其實就是為了分享這段特殊的「反向表列」寫法而已。

最後,如果我們只想針對 IndexB 這個名稱當成路由限制條件,其路由定義如下:

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
    constraints: new { controller = "(?!^IndexB$).*" }
);

我利用規則運算式中的 零寬度右不合樣 (Negative Lookahead) 語法,來定義出一個「反向表列」的規則,其中零寬度右不合樣的比對規則中,還要加上 ^$ 符號,才能真正做到 100% 的「反向比對」,否則定義出來的樣式會導致「部分比對」的情況發生。
( 註: 我有另外一篇類似的文章可供讀者參考: 使用 Regular Expression 驗證密碼 )

如果你想要「反向表列」兩個以上的名稱,其範例如下:

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
    constraints: new { controller = "(?!^IndexB$)(?!^MyHandler$).*" }
);

 

 

本文範例程式專案已上傳至 GitHub,各位可以多加利用 GitHub for Windows 工具下載原始碼回去測試:

 

 

相關連結