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

我曾經在【[ASP.NET MVC 開發心得分享 (21)：Routing 觀念與技巧](/post/2011/08/01/ASPNET-MVC-Developer-Note-Part-21-Routing-Concepts-and-Skills.aspx)】這篇文章中分享過幾個**路由開發技巧**，其中在【**技巧 1：替 Routing 網址設立條件限制**】的部分有示範如何透過簡單的 RegEx [規則運算式](http://msdn.microsoft.com/zh-tw/library/hs600312\(v=vs.90\).aspx) (正則表達式) 來限制**路由變數**的內容規則。不過，通常你在網路上能查到的這些 **路由限制** (Route Constraints) 範例，大多使用「**正向表列**」的方式進行比對，這的確在大部分開發情境下都是這樣用的，但在特定比較少見的開發情境下，你或許需要「**反向表列**」的方式來限定路由參數的比對規則，尤其是在 **ASP.NET MVC** 與 **ASP.NET HttpHandler** 混合執行的情況下，更容易遇到這樣的問題。

**執行環境描述：**

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

-   組件名稱：**RouteConstraintsSample**
-   命名空間：**RouteConstraintsSample.HttpHandlers**
-   類別名稱：**IndexB**

```csharp
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** 必須這樣定義：

```xml
<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 處理常式，你將會得到以下錯誤畫面：

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/a33be5c6fae388d75b49-image_2061.png)

然而，會發生這個錯誤，最主要的原因還是來自於 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 條件限制](/post/2011/08/01/ASPNET-MVC-Developer-Note-Part-21-Routing-Concepts-and-Skills.aspx)的方式寫出來 (透過一個自訂類別並實做 IRouteConstraint 介面的方式)，但總覺得不死心，問我用 RegEx 真的可以寫出「反向表列」的規則定義嗎？

正所謂江湖一點訣，看得懂 RegEx [規則運算式](http://msdn.microsoft.com/zh-tw/library/hs600312\(v=vs.90\).aspx)是一種能夠閱讀**天書**的能力，但寫得出來才能算是真正有實力，大家都應該好好練練。這話說得不誇張，我在十幾年前玩 [Perl](http://www.perl.org/) 的時候，曾經有一整年的時間經常接觸 RegEx [規則運算式](http://msdn.microsoft.com/zh-tw/library/hs600312\(v=vs.90\).aspx)，所以對語法瞭若指掌，當我被問及這個需求時，還真的也想了一些時間才寫出來，今天這篇文章寫得這麼長，其實就是為了分享這段特殊的「反向表列」寫法而已。

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

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

我利用[規則運算式](http://msdn.microsoft.com/zh-tw/library/az24scfc.aspx)中的 **零寬度右不合樣 (Negative Lookahead)** 語法，來定義出一個「反向表列」的規則，其中**零寬度右不合樣**的比對規則中，還要加上 ^ 與 $ 符號，才能真正做到 100% 的「反向比對」，否則定義出來的樣式會導致「部分比對」的情況發生。  
( 註: 我有另外一篇類似的文章可供讀者參考: [使用 Regular Expression 驗證密碼](/post/2008/05/09/Using-Regular-Expression-to-validate-password.aspx) )

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

```
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](http://windows.github.com/) 工具下載原始碼回去測試：

-   [**https://github.com/doggy8088/RouteConstraintsSample**](https://github.com/doggy8088/RouteConstraintsSample)
-   **註**：請使用 Visual Studio 2012 開啟本專案。如果無法成功建置，請先更新 NuGet 到最新版。

**相關連結**

-   [ASP.NET MVC 開發心得分享 (21)：Routing 觀念與技巧](/post/2011/08/01/ASPNET-MVC-Developer-Note-Part-21-Routing-Concepts-and-Skills.aspx)
-   [Creating a Route Constraint (C#)](http://www.asp.net/mvc/tutorials/controllers-and-routing/creating-a-route-constraint-cs)
-   [規則運算式語言 - 快速參考](http://msdn.microsoft.com/zh-tw/library/az24scfc.aspx)
-   Debuggex: The online visual regex tester
-   [HOW TO：登錄 HTTP 處理常式](http://msdn.microsoft.com/zh-tw/library/46c5ddfy\(v=vs.100\).aspx)
