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 }
)
}
這裡分別對 IgnoreRoute
與 MapRoute
去做說明
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-router、vue-router、angular 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 使用