demoshop

demo, trying to be the best_

在 demo 講 ASP.NET MVC 的課程時,我都會很早就提到網址路由(Route) 的部分,不過只是簡單的說一下預設的對應方式,因為不會自訂網址路由(Route)並不會影響整個網站的開發,頂多就是做出來以後網址有點醜而已....

但是當開發者已經進階到開始要改網址路由(Route)時,就會發現這裡面學問還真多,而本篇不是要詳細的說明基本的部分(基本說明請參考《ASP.NET MVC4網站開發美學》),只是要介紹如何藉由實做 IRouteConstraint 來自訂約束(constraints)條件,當你學會了自訂約束條件後,網址路由設定的難度就會相對簡單很多。

demo廢言現在 demo 開發網站幾乎都用 GUID 來做網站的唯一識別碼,以下拿最近利用空暇時間開發的 Best Gallery 網站來做示範。因為使用了 GUID 所以在預設的情況下網址應該都會長這樣

http://vs.demo.tc/Gallery/Index/9f819024-3fea-43d5-afdd-e8c2ccad73b7

這種網址就是前文提到的「醜」,中間多了一個很無意義的 Index 因此必須做點改變。


注意事項本範例使用 ASP.NET MVC4

 

●開啟 App_Start 資料夾內的 RouteConfig.cs 可以看到預設路由

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

想拿掉 Index 這個累贅,可以使用下面這段路由定義。

routes.MapRoute(
    name: "Gallery",
    url: "Gallery/{id}",
    defaults: new {controller = "Gallery", action = "Index", id = UrlParameter.Optional},
    constraints:new{id =@"^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$"},
    );

因為要避免太容易匹配成功,所以設計路由時會利用 Constraints 來增加條件約束,本範例約束了 id 這個參數必須要符合 GUID 的格式,而 GUID 的格式使用了 Regex 來描述。

注意事項網址路由為由上而下匹配,匹配到就離開,因此請把新增的路由定義放在第一個位置。


demo廢言 好!這樣子事情就解決了,現在可以看到自動產出的網址變成

http://vs.demo.tc/Gallery/9f819024-3fea-43d5-afdd-e8c2ccad73b7

需求達成了,本文就結束了嗎?當然不是囉,各位讀者可以看到上面使用了 Regex 描述了 GUID 的格式,但是對於初學者來說 Regex 是屬於天書等級的存在,雖然 demo 一直強調看懂 Regex 是一個很重要的事情,但是這次讓我們改用更好更正規的作法,先拋棄 Regex 吧。


●在 MVC 中允許我們利用實做 IRouteConstraint 這個介面來設計自訂的路由約束條件,現在就新增一個吧

public class GuidConstraint : IRouteConstraint
{
    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values,
                        RouteDirection routeDirection)
    {
        if (values.ContainsKey(parameterName))
        {
            var guid = values[parameterName] as Guid?;
            if (guid.HasValue == false)
            {
                var stringValue = values[parameterName] as string;
                if (string.IsNullOrWhiteSpace(stringValue)==false)
                {
                    Guid parsedGuid;
                    // .NET 4 新增的 Guid.TryParse
                    Guid.TryParse(stringValue, out parsedGuid);
                    guid = parsedGuid;
                }
            }
            return (guid.HasValue && guid.Value != Guid.Empty);
        }
        return false;
    }
}

注意事項以上的 Code 可以直接複製貼上使用(因為最近有人一直噹我,說我 Blog 的 Code 都不能複製貼上,所以特別放了一個可以複製貼上就完成的 Code)


●接下來去改掉剛剛的路由定義,讓它變成這樣

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

漂亮吧,天書的部分完全拿掉了,而且因為命名正確,所以任何人一看就知道 id 被約束成 GUID


demo廢言藉由自訂路由的約束定義,可以讓開發者在定義路由的時候更隨心所欲,因為你現在可以寫程式去做驗證,那絕對是好寫很多,就算你想要限制只有 Home 和 Account 的 Controller 才吃這路由也可以輕鬆的寫

public class OnlyHomeAndAccountConstraint : IRouteConstraint
{
    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values,
                        RouteDirection routeDirection)
    {
        string[] constraintName = new string[] { "Home", "Account" };
        if (values.ContainsKey(parameterName))
        {
           var stringValue = values[parameterName] as string;

                //不考慮大小寫的比對方式

                return Array.Exists(constraintName, val => val.Equals(stringValue, StringComparison.InvariantCultureIgnoreCase));

        }
        return false;
    }
}

甚至是經常會用到的日期也是沒問題的

    public class DateTimeConstraint : IRouteConstraint
    {
        public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values,
                            RouteDirection routeDirection)
        {
            var dateTime = values[parameterName] as DateTime?;
            if (dateTime.HasValue == false)
            {
                var stringValue = values[parameterName] as string;
                if (string.IsNullOrWhiteSpace(stringValue) == false)
                {
                    DateTime parsedDateTime;
                    DateTime.TryParse(stringValue, out parsedDateTime);
                    dateTime = parsedDateTime;
                }
            }
            return (dateTime.HasValue && dateTime.Value != default(DateTime));
        }
    }
 

注意事項本文還有一個小範例放置於 GitHub 如果有興趣可以去下載回來玩玩看

https://github.com/demofan/demo_tc_786

 

 

 

demo廢言因為被人噹所以這篇一次放上了三個可以直接複製貼上的code,希望下次 twMVC 每週四固定聚會不會在被噹了

ASP.NET MVC Routes 技巧 系列文章

回應討論