URL Rewriting 與 Route 基本概念

  • URL Rewriting 屬於 REST 技術(Representational State Transfer)的一部分
  • 提供使用者更明確、直覺的網址
  • SEO 較佳

以往使用 Query String 的方式傳遞參數:

http://Localhost/Products/ProductDetail.aspx?kind=sports&id=3

使用 URL Rewriting 後,參數會與網址結合,增加了可讀性:

http://Lodcalhost/Products/Detail/sports/3

ASP.NET MVC 利用 RouteTable.Routes 靜態物件將所有的路由規則儲存起來

當收到 HTTP request 的時候,就會比對 RouteTable.Routes 內的路由規則

來決定使用 MvcHandler 來處理,或使用其他的 HttpHandler

URL Routing 基本應用

RouteConfig.cs 做 routing 的設定(位於 App_Start 資料匣裡)

創建一個 ASP.NET 專案之後,預設的 routing 會長這個樣子:

public static void RegisterRoutes(RouteCollection routes)
{
  routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

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

這裡分別對 IgnoreRouteMapRoute 去做說明

IgnoreRoute:要忽略的路由規則

以上面預設 routing 的例子:

routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

IgnoreRoute 是我們要忽略的 routing 規則

{resource}{*pathInfo} 代表的是佔位符

因此,.axd 結尾的 route 都要忽略

另外,{*pathInfo} 中的星號表示取得全部

例如網址是 test.axd/detail/sports/3

  • {pathInfo} 會比對到 detail
  • {*pathInfo} 則會比對到 detail/sports/3

MapRoute 要匹配的路由規則

MapRoute 則定義特定的 url 導至對應的 controller

承剛才的預設 routing:

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

MapRoute 帶入的參數有三個:

  • name:路由的名稱,注意必須是唯一值
  • url:url 的部分就跟 IgnoreRoute一樣
  • defaults:給這個路由各個參數的預設值,當比對不到的時候,就拿 defaults 對應欄位的預設值。另外,id 的預設值 UrlParameter.Optional 則代表著若網址沒有給 id 也是可以通過路由匹配

另外,值得注意的是,MapRoute 帶入的參數是使用了 C#4.0 新增的具名參數(Named Arguments)的方式來帶入參數

因為是具名,所以參數的順序就不一定要一樣,在開發上更為友善

關於具名參數詳細的介紹,可以參考微軟的文件

此外,URL Routing 仍然可以搭配 QueryString 一起使用,不會有任何影響:

http://localhost/Products/List?page=3

URL Routing 如何比對

route 比對按照順序,由上而下,匹配到之後,就不會繼續往下尋找

原理其實就跟 react-routervue-routerangular router 很相似

為 URL Routing 加上限制條件

為了避免被預期外的 URL Routing 匹配成功,在 MapRoute() 裡,可以透過

  • Constraints 條件約束
  • Namespaces 命名空間

這兩種方式為它加上限制條件

Constraints:條件驗證

Constraints 可以針對我們要限制的條件作設定,例如:限制特定的 Controller 名稱、Action 名稱

此外,限制條件也可以用正規表達式來寫

以下對 action 做約束:

routes.MapRoute(
  // ...
  // 一般寫法
  constraints: new { action = "About" },
  // 或用正規表達式
  constraints: new { action = "(About|Contact)" }
)

備註:在 constraints 裡,正規表達式是完整比對,因此比對時會變成 ^(About|Contact)$,在寫的時候不用自己加上「^」與「$」

如果這個限制條件複雜,正規表達式也會隨之複雜而難以閱讀

此時,可以建立一個 class 並實作 IRouteConstraint 這個 interface

自行擴充的 constraint class 不僅將驗證的過程抽離獨立出來

也同時可以加強驗證的嚴謹度

因為正規表達式只能驗證格式,但在 class 裡就可以驗證到邏輯

然後再置入 constraints 裡:

routes.MapRoute(
  // ...
  constraints: new { id = new MyConstraint() },
)

這個 class 的詳細實作細節就不在這裡討論了,有興趣可以再自行深入了解,例如這個範例

Namespaces:命名空間限制

定義 namespaces 的限制之後,就只有符合的 namespaces 才會做匹配:

routes.MapRoute(
  // ...
  namespaces: new[] { "Namespace1.Controllers" },
)

namespaces 的限制會跟 ASP.NET MVC 中的 Area 架構有關

備註:Route 的這兩個限制條件是可以同時使用

Area

書中對於 Area 的描述如下:

在 MVC 專案中開闢一個區域放置一個功能模組,而這個被切出來的區域包含專屬的 Model、View、Controller,同時也有 Area 自己獨立的 Route 及 View Engine

看了這個描述,就跟 Angular 的 module 有點類似

每個 Angular module 都擁有自己的 component、service、route

而每個 Area 都有自己的 namespace

假設我們在 WebApplication1 專案底下建立一個名為 TestArea 的 Area

則它的 namespace 就會是 WebApplication1.Areas.TestArea

剛才提到的命名空間限制就是在這裡所使用

若專案建立了很多的 Area,就會有相應的 namespace

同時,每個 Area 都有屬於自己的 controller、action

因此可以在 MapRoute 做設定,指定這個 route 給特定的 namespace,亦即特定 Area 底下的 controller 使用

參考資料