[ASP.NET MVC 小牛之路]07 - URL Routing

我們知道在ASP.NET Web Forms中,一個URL請求往往對應一個aspx頁面,一個aspx頁面就是一個物理文件,它包含對請求的處理。

而在ASP.NET MVC中,一個URL請求是由對應的一個Controller中的Action來處理的,由URL Routing來告訴MVC如何定位到正確的Controller和Action。

籠統的講,URL Routing包含兩個主要功能:解析URL 和 生成URL,本文將圍繞這兩個大點進行講解。

本文目錄

URL Routing 的定義方式

讓我們從下面這樣一個簡單的URL開始:

http://mysite.com/Admin/Index

在域名的後面,默認使用“/”來對URL進行分段。路由系統通過類似於 {controller}/{action} 格式的字符串可以知道這個URL的 Admin 和 Index 兩個片段分別對應Controller和Action的名稱。

默認情況下,路由格式中用“/”分隔的段數是和URL域名的後面的段數是一致的,比如,對於{controller}/{action} 格式只會匹配兩個片段。如下表所示:

URL路由是在MVC工程中的App_Start文件夾下的RouteConfig.cs文件中的RegisterRoutes方法中定義的,下面是創建一個空MVC項目時系統生成的一個簡單URL路由定義:

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 } 
    );
}

靜態方法RegisterRoutes是在Global.asax.cs文件中的Application_Start方法中被調用的,除了URL路由的定義外,還包含其他的一些MVC核心特性的定義:

protected void Application_Start() { 
    AreaRegistration.RegisterAllAreas();

    WebApiConfig.Register(GlobalConfiguration.Configuration); 
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); 
    RouteConfig.RegisterRoutes(RouteTable.Routes); 
    BundleConfig.RegisterBundles(BundleTable.Bundles); 
}

RouteConfig.RegisterRoutes方法中傳遞的是 RouteTable 類的靜態 Routes 屬性,返回一個RouteCollection的實例。其實,“原始”的定義路由的方法可以這樣寫:

public static void RegisterRoutes(RouteCollection routes) { 

    Route myRoute = new Route("{controller}/{action}", new MvcRouteHandler()); 
    routes.Add("MyRoute", myRoute); 
}

創建Route對象時用了一個URL格式字符串和一個MvcRouteHandler對象作爲構造函數的參數。不同的ASP.NET技術有不同的RouteHandler,MVC用的是MvcRouteHandler。

這種寫法有點繁瑣,一種更簡單的定義方法是:

public static void RegisterRoutes(RouteCollection routes) { 

    routes.MapRoute("MyRoute", "{controller}/{action}"); 
}

這種方法簡潔易讀,一般我們都會用這種方法定義路由。 

示例準備

作爲演示,我們先來準備一個Demo。創建一個標準的MVC應用程序,然後添加三個簡單的Controller,分別是HomeController、CustomerController和AdminController,代碼如下:

public class HomeController : Controller {
            
    public ActionResult Index() {
        ViewBag.Controller = "Home";
        ViewBag.Action = "Index";
        return View("ActionName");
    }
}
public class CustomerController : Controller {
        
    public ActionResult Index() {
        ViewBag.Controller = "Customer";
        ViewBag.Action = "Index";
        return View("ActionName");
    }

    public ActionResult List() {
        ViewBag.Controller = "Customer";
        ViewBag.Action = "List";
        return View("ActionName");
    }
}
public class AdminController : Controller {
        
    public ActionResult Index() {
        ViewBag.Controller = "Admin";
        ViewBag.Action = "Index";
        return View("ActionName");
    }
}

在 /Views/Shared 文件夾下再給這三個Controller添加一個共享的名爲 ActionName.cshtml 的 View,代碼如下:

@{ 
    Layout = null; 
}

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>ActionName</title>
</head>
<body>
    <div>The controller is: @ViewBag.Controller</div>
    <div>The action is: @ViewBag.Action</div>
</body>
</html>

我們把RouteConfig.cs文件中項目自動生成的URL Rounting的定義刪了,然後根據前面講的路由定義知識,我們自己寫一個最簡單的:

public static void RegisterRoutes(RouteCollection routes) { 

    routes.MapRoute("MyRoute", "{controller}/{action}"); 
}

程序運行,URL定位到 Admin/Index 看看運行結果:

這個Demo輸出的是被調用的Controller和Action名稱。

給片段變量定義默認值

在上面我們必須把URL定位到特定Controller和Action,否則程序會報錯,因爲MVC不知道去執行哪個Action。 我們可以通過指定默認值來告訴MVC當URL沒有給出對應的片段時使用某個默認的值。如下給controller和action指定默認值:

routes.MapRoute("MyRoute", "{controller}/{action}",  new { controller = "Home", action = "Index" });

這時候如果在URL中不提供action片段的值或不提供controller和action兩個片段的值,MVC將使用路由定義中提供的默認值:

它的各種匹配情況如下表所示:

注意,對於上面的URL路由的定義,我們可以只給action一個片段指定默認值,但是不能只給controller一個片段指定默認值,即如果我們給Controller指定了默認值,就一定也要給action指定默認值,否則URL只有一個片段時,這個片段匹配給了controller,action將找不到匹配。

定義靜態片段

並不是所有的片段都是用來作爲匹配變量的,比如,我們想要URL加上一個名爲Public的固定前綴,那麼我們可以這樣定義:

routes.MapRoute("", "Public/{controller}/{action}",  new { controller = "Home", action = "Index" });

這樣,請求的URL也需要一個Public前綴與之匹配。我們也可以把靜態的字符串放在大括號以外的任何位置,如:

routes.MapRoute("", "X{controller}/{action}",  new { controller = "Home", action = "Index" });

在一些情況下這種定義非常有用。比如當你的網站某個鏈接已經被用戶普遍記住了,但這一塊功能已經有了一個新的版本,但調用的是不同名稱的controller,那麼你把原來的controller名稱作爲現在controller的別名。這樣,用戶依然使用他們記住的URL,而導向的卻是新的controller。如下使用Shop作爲Home的一個別名:

routes.MapRoute("ShopSchema", "Shop/{action}",  new { controller = "Home" }); 

這樣,用戶使用原來的URL可以訪問新的controller:

自定義片段變量

自定義片段變量的定義和取值

contrlloer和action片段變量對MVC來說有着特殊的意義,在定義一個路由時,我們必須有這樣一個概念:contrlloer和action的變量值要麼能從URL中匹配得到,要麼由默認值提供,總之一個URL請求經過路由系統交給MVC處理時必須保證contrlloer和action兩個變量的值都有。當然,除了這兩個重要的片段變量,我們也可從通過自定義片段變量來從URL中得到我們想要的其它信息。如下自定義了一個名爲Id的片段變量,而且給它定義了默認值:

routes.MapRoute("MyRoute", "{controller}/{action}/{id}",
    new {
        controller = "Home",
        action = "Index",
        id = "DefaultId"
});

我們在HomeController中增加一個名爲CustomVariable的ACtion來演示一下如何取自定義的片段變量:

public ActionResult CustomVariable() {
    ViewBag.Controller = "Home";
    ViewBag.Action = "CustomVariable";
    ViewBag.CustomVariable = RouteData.Values["id"];
    return View("ActionName");
}

可以通過 RouteData.Values[segment] 來取得任意一個片段的變量值。

再稍稍改一下ActionName.cshtml 來看一下我們取到的自定義片段變量的值:

...
<div>The controller is: @ViewBag.Controller</div> 
<div>The action is: @ViewBag.Action</div> 
<div>The custom variable is: @ViewBag.CustomVariable</div>
...

將URL定位到 /Home/CustomVariable/Hello 將得到如下結果:

自定義的片段變量用處很大,也很靈活,下面介紹一些常見的用法。

將自定義片段變量作爲Action方法的參數

我們可以將自定義的片段變量當作參數傳遞給Action方法,如下所示:

public ActionResult CustomVariable(string id) { 
    ViewBag.Controller = "Home"; 
    ViewBag.Action = "CustomVariable"; 
    ViewBag.CustomVariable = id; 
    return View("ActionName"); 
}

效果和上面是一樣的,只不過這樣省去了用 RouteData.Values[segment] 的方式取自定義片段變量的麻煩。這個操作背後是由模型綁定來做的,模型綁定的知識我將在後續博文中進行講解。

指定自定義片段變量爲可選

指定自定片段變量爲可選,即在URL中可以不用指定片段的值。如下面的定義將Id定義爲可選:

routes.MapRoute("MyRoute", "{controller}/{action}/{id}", new {
        controller = "Home",
        action = "Index",
        id = UrlParameter.Optional
});

定義爲可選以後,需要對URL中沒有Id這個片段值的情況進行處理,如下:

public ActionResult CustomVariable(string id) { 
    ViewBag.Controller = "Home"; 
    ViewBag.Action = "CustomVariable"; 
    ViewBag.CustomVariable = id == null ? "<no value>" : id; 
    return View("ActionName"); 
} 

當Id是整型的時候,參數的類型需要改成可空的整型(即int? id)。

爲了省去判斷參數是否爲空,我們也可以把Action方法的id參數也定義爲可選,當沒有提供Id參數時,Id使用默認值,如下所示:

public ActionResult CustomVariable(string id = "DefaultId") { 
    ViewBag.Controller = "Home"; 
    ViewBag.Action = "CustomVariable"; 
    ViewBag.CustomVariable = id; 
    return View("ActionName"); 
}

這樣其實就是和使用下面這樣的方式定義路由是一樣的:

routes.MapRoute("MyRoute", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = "DefaultId" });

定義可變數量的自定義片段變量

我們可以通過 catchall 片段變量加 * 號前綴來定義匹配任意數量片段的路由。如下所示:

routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}", 
    new { controller = "Home", action = "Index",  id = UrlParameter.Optional });

這個路由定義的匹配情況如下所示:

使用*catchall,將匹配的任意數量的片段,但我們需要自己通過“/”分隔catchall變量的值來取得獨立的片段值。

路由約束

正則表達式約束

通過正則表達式,我們可以制定限制URL的路由規則,下面的路由定義限制了controller片段的變量值必須以 H 打頭:

routes.MapRoute("MyRoute", "{controller}/{action}/{id}", 
    new { controller = "Home", action = "Index", id = UrlParameter.Optional },
    new { controller = "^H.*" }
);

定義路由約束是在MapRoute方法的第四個參數。和定義默認值一樣,也是用匿名類型。

我們可以用正則表達式約束來定義只有指定的幾個特定的片段值才能進行匹配,如下所示:

routes.MapRoute("MyRoute", "{controller}/{action}/{id}", 
    new { controller = "Home", action = "Index", id = UrlParameter.Optional },
    new { controller = "^H.*", action = "^Index$|^About$" }
);

這個定義,限制了action片段值只能是Index或About,不區分大小寫。

Http請求方式約束

我們還可以限制路由只有當以某個特定的Http請求方式才能匹配。如下限制了只能是Get請求才能進行匹配:

routes.MapRoute("MyRoute", "{controller}/{action}/{id}", 
    new { controller = "Home", action = "Index", id = UrlParameter.Optional },
    new { controller = "^H.*", httpMethod = new HttpMethodConstraint("GET") }
);

通過創建一個 HttpMethodConstraint 類的實例來定義一個Http請求方式約束,構造函數傳遞是允許匹配的Http方法名。這裏的httpMethod屬性名不是規定的,只是爲了區分。

這種約束也可以通過HttpGet或HttpPost過濾器來實現,後續博文再講到濾器的內容。

自定義路由約束

如果標準的路由約束滿足不了你的需求,那麼可以通過實現 IRouteConstraint 接口來定義自己的路由約束規則。

我們來做一個限制瀏覽器版本訪問的路由約束。在MVC工程中添加一個文件夾,取名Infrastructure,然後添加一個 UserAgentConstraint 類文件,代碼如下:

public class UserAgentConstraint : IRouteConstraint {
        
    private string requiredUserAgent;

    public UserAgentConstraint(string agentParam) {
        requiredUserAgent = agentParam;
    }

    public bool Match(HttpContextBase httpContext, Route route, string parameterName,
        RouteValueDictionary values, RouteDirection routeDirection) {
            
        return httpContext.Request.UserAgent != null 
            && httpContext.Request.UserAgent.Contains(requiredUserAgent);
    }
}

這裏實現IRouteConstraint的Match方法,返回的bool值告訴路由系統請求是否滿足自定義的約束規則。我們的UserAgentConstraint類的構造函數接收一個瀏覽器名稱的關鍵字作爲參數,如果用戶的瀏覽器包含註冊的關鍵字纔可以訪問。接一來,我們需要註冊自定的路由約束:

public static void RegisterRoutes(RouteCollection routes) {

    routes.MapRoute("ChromeRoute", "{*catchall}",
        new { controller = "Home", action = "Index" },
        new { customConstraint = new UserAgentConstraint("Chrome") }
    );
}

下面分別是IE10和Chrome瀏覽器請求的結果:

     

定義請求磁盤文件路由

並不是所有的URL都是請求controller和action的。有時我們還需要請求一些資源文件,如圖片、html文件和JS庫等。

我們先來看看能不能直接請求一個靜態Html文件。在項目的Content文件夾下,添加一個html文件,內容隨意。然後把URL定位到該文件,如下圖:

我們看到,是可以直接訪問一靜態資源文件的。

默認情況下,路由系統先檢查URL是不是請求靜態文件的,如果是,服務器直接返回文件內容並結束對URL的路由解析。我們可以通過設置 RouteCollection的 RouteExistingFiles 屬性值爲true 讓路由系統對靜態文件也進行路由匹配,如下所示:

public static void RegisterRoutes(RouteCollection routes) {
    
    routes.RouteExistingFiles = true;

    routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",
        new { controller = "Home", action = "Index", id = UrlParameter.Optional
    });
}

設置了routes.RouteExistingFiles = true後,還需要對IIS進行設置,這裏我們以IIS Express爲例,右鍵IIS Express小圖標,選擇“顯示所有應用程序”,彈出如下窗口:

點擊並打開配置文件,Control+F找到UrlRoutingModule-4.0,將這個節點的preCondition屬性改爲空,如下所示:

<add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" preCondition=""/>

然後我們運行程序,再把URL定位到之前的靜態文件:

這樣,路由系統通過定義的路由去匹配RUL,如果路由中沒有定義該靜態文件的匹配,則會報上面的錯誤。

一旦定義了routes.RouteExistingFiles = true,我們就要爲靜態文件定義路由,如下所示:

public static void RegisterRoutes(RouteCollection routes) {
    
    routes.RouteExistingFiles = true;

    routes.MapRoute("DiskFile", "Content/StaticContent.html",
        new { controller = "Customer", action = "List", });

    routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",
        new { controller = "Home", action = "Index", id = UrlParameter.Optional });
}

這個路由匹配Content/StaticContent.html的URL請求爲controller = Customer, action = List。我們來看看運行結果:

這樣做的目的是爲了可以在Controller的Action中控制對靜態資源的請求,並且可以阻止對一些特殊資源文件的訪問。

設置了RouteExistingFiles屬性爲true後,我們要爲允許用戶請求的資源文件進行路由定義,如果每種資源文件都去定義相應的路由,就會顯得很繁瑣。

我們可以通過RouteCollection類的IgnoreRoute方法繞過路由定義,使得某些特定的靜態文件可以由服務器直接返回給給瀏覽器,如下所示:

public static void RegisterRoutes(RouteCollection routes) {
    
    routes.RouteExistingFiles = true;

    routes.IgnoreRoute("Content/{filename}.html");

    routes.MapRoute("DiskFile", "Content/StaticContent.html",
        new { controller = "Customer", action = "List", });

    routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",
        new { controller = "Home", action = "Index", id = UrlParameter.Optional });
}

這樣,只要是請求Content目錄下的任何html文件都能被直接返回。這裏的IgnoreRoute方法將創建一個RouteCollection的實例,這個實例的Route Handler 爲 StopRoutingHandler,而不是 MvcRouteHandler。運行程序定位到Content/StaticContent.html,我們又看到了之前的靜態面面了。

生成URL(鏈接)

前面講的都是解析URL的部分,現在我們來看看如何通過路由系統在View中生成URL。

生成指向當前controller的action鏈接

在View中生成URL的最簡單方法就是調用Html.ActionLink方法,如下面在 Views/Shared/ActionName.cshtml 中的代碼所示:

...
<div>The controller is: @ViewBag.Controller</div>
<div>The action is: @ViewBag.Action</div>
<div>
    @Html.ActionLink("This is an outgoing URL", "CustomVariable")
</div>
...

這裏的Html.ActionLink方法將會生成指向View對應的Controller和第二個參數指定的Action,我們可以看看運行後頁面是如何顯示的:

經過查看Html源碼,我們發現它生成了下面這樣的一個html鏈接:

<a href="/Home/CustomVariable">This is an outgoing URL</a> 

這樣看起來,通過Html.ActionLink生成URL似乎並沒有直接在View中自己寫一個<a>標籤更直接明瞭。 但它的好處是,它會自動根據路由配置來生成URL,比如我們要生成一個指向HomeContrller中的CustomVariable Action的連接,通過Html.ActionLink方法,只需要給出對應的Controller和Action名稱就行,我們不需要關心實際的URL是如何組織的。舉個例子,我們定義了下面的路由:

public static void RegisterRoutes(RouteCollection routes) {
            
    routes.MapRoute("NewRoute", "App/Do{action}", new { controller = "Home" });
            
    routes.MapRoute("MyRoute", "{controller}/{action}/{id}",
        new { controller = "Home", action = "Index", id = UrlParameter.Optional });
}

運行程序,我們發現它會自動生成下面這樣的連接:

<a href="/App/DoCustomVariable">This is an outgoing URL</a>

所以我們要生成指向某個Action的鏈接時,最好使用Html.ActionLink方法,否則你很難保證你手寫的連接就能定位到你想要的Action。

生成其他controller的action鏈接

上面我們給Html.ActionLink方法傳遞的第二個參數只告訴了路由系統要定位到當前View對應的Controller下的Action。Html.ActionLink方法可以使用第三個參數來指定其他的Controller,如下所示:

<div> 
    @Html.ActionLink("This targets another controller", "Index", "Admin") 
</div> 

它會自動生成如下鏈接:

<a href="/Admin">This targets another controller</a> 

生成帶有URL參數的鏈接

有時候我們想在連接後面加上參數以傳遞數據,如 ?id=xxx 。那麼我們可以給Html.ActionLink方法指定一個匿名類型的參數,如下所示:

<div>
    @Html.ActionLink("This is an outgoing URL", "CustomVariable", new { id = "Hello" })
</div>

它生成的Html如下:

<a href="/Home/CustomVariable/Hello">This is an outgoing URL</a>

指定鏈接的Html屬性

通過Html.ActionLink方法生成的鏈接是一個a標籤,我們可以在方法的參數中給標籤指定Html屬性,如下所示:

<div> 
    @Html.ActionLink("This is an outgoing URL",  "Index", "Home", null, 
        new {id = "myAnchorID", @class = "myCSSClass"})
</div>

這裏的class加了@符號,是因爲class是C#關鍵字,@符號起到轉義的作用。它生成 的Html代碼如下:

<a class="myCSSClass" href="/" id="myAnchorID">This is an outgoing URL</a>

生成完整的標準鏈接

前面的都是生成相對路徑的URL鏈接,我們也可以通過Html.ActionLink方法生成完整的標準鏈接,方法如下:

<div> 
    @Html.ActionLink("This is an outgoing URL", "Index", "Home", 
        "https", "myserver.mydomain.com", " myFragmentName",
        new { id = "MyId"},
        new { id = "myAnchorID", @class = "myCSSClass"})
</div>

這是Html.ActionLink方法中最多參數的重載方法,它允許我們提供請求的協議(https)和目標服務器地址(myserver.mydomain.com)等。它生成的鏈接如下:

<a class="myCSSClass" id="myAnchorID"
    href="https://myserver.mydomain.com/Home/Index/MyId#myFragmentName" >
    This is an outgoing URL</a>

生成URL字符串

用Html.ActionLink方法生成一個html鏈接是非常有用而常見的,如果要生成URL字符串(而不是一個Html鏈接),我們可以用 Url.Action 方法,使用方法如下:

<div>This is a URL: 
    @Url.Action("Index", "Home", new { id = "MyId" }) 
</div> 

它顯示到頁面是這樣的:

根據指定的路由名稱生成URL

我們可以根據某個特定的路由來生成我們想要的URL,爲了更好說明這一點,下面給出兩個URL的定義:

public static void RegisterRoutes(RouteCollection routes) { 
    routes.MapRoute("MyRoute", "{controller}/{action}"); 
    routes.MapRoute("MyOtherRoute", "App/{action}", new { controller = "Home" }); 
} 

對於這樣的兩個路由,對於類似下面這樣的寫法:

@Html.ActionLink("Click me", "Index", "Customer")

始終會生成這樣的鏈接:

<a href="/Customer/Index">Click me</a>

也就是說,永遠無法使用第二個路由來生成App前綴的鏈接。這時候我們需要通過另一個方法Html.RouteLink來生成URL了,方法如下:

@Html.RouteLink("Click me", "MyOtherRoute","Index", "Customer")

它會生成如下鏈接:

<a Length="8" href="/App/Index?Length=5">Click me</a>

這個鏈接指向的是HomeController下的Index Action。但需要注意,通過這種方式來生成URL是不推薦的,因爲它不能讓我們從直觀上看到它生成的URL指向的controller和action。所以,非到萬不得已的情況纔會這樣用。

在Action方法中生成URL

通常我們一般在View中才會去生成URL,但也有時候我們需要在Action中生成URL,方法如下:

public ViewResult MyActionMethod() { 
    
    string myActionUrl = Url.Action("Index", new { id = "MyID" }); 
    string myRouteUrl = Url.RouteUrl(new { controller = "Home", action = "Index" }); 
    
    //... do something with URLs... 
    return View(); 
}

其中 myActionUrl 和 myRouteUrl 將會被分別賦值 /Home/Index/MyID 和 / 。

更多時候我們會在Action方法中將客戶端瀏覽器重定向到別的URL,這時候我們使用RedirectToAction方法,如下:

public RedirectToRouteResultMyActionMethod() { 
    return RedirectToAction("Index");
}

RedirectToAction的返回結果是一個RedirectToRouteResult類型,它使MVC觸發一個重定向行爲,並調用指定的Action方法。RedirectToAction也有一些重載方法,可以傳入controller等信息。也可以使用RedirectToRoute方法,該方法傳入的是object匿名類型,易讀性強,如:

public RedirectToRouteResult MyActionMethod() {
    return RedirectToRoute(new { controller = "Home", action = "Index", id = "MyID" });
}

URL方案最佳實踐

下面是一些使用URL的建議:

  1. 最好能直觀的看出URL的意義,不要用應用程序的具體信息來定義URL。比如使用 /Articles/Report 比使用 /Website_v2/CachedContentServer/FromCache/Report 好。

  2. 使用內容標題比使用ID好。比如使用 /Articles/AnnualReport 比使用 /Articles/2392 好。如果一定要使用使用ID(比如有時候可能需要區分相同的標題),那麼就兩者都用,如 /Articles/2392/AnnualReport ,它看起來很長,但對用戶更友好,而且更利於SEO。

  3. 對於Web頁面不要使用文件擴展名(如 .aspx 或 .mvc)。但對於特殊的文件使用擴展名(如 .jpg、.pdf 和 .zip等)。

  4. 儘可能使用層級關係的URL,如 /Products/Menswear/Shirts/Red,這樣用戶就能猜到父級URL。

  5. 不區分大小寫,這樣方便用戶輸入。

  6. 正確使用Get和Post。Get一般用來從服務器獲取只讀的信息,當需要操作更改狀態時使用Post。

  7. 儘可能避免使用標記符號、代碼、字符序列等。如果你想要用標記進行分隔,就使用中劃線(如 /my-great-article),下劃線是不友好的,另外空格和+號都會被URL編碼。

  8. 不要輕易改變URL,尤其對於互聯網網站。如果一定要改,那也要儘可能長的時間保留原來的URL。

  9. 儘量讓URL使用統一的風格或習慣。

 


參考:

  《Pro ASP.NET MVC 4 4th Edition》

  http://msdn.microsoft.com/en-us/library/cc668201.ASPX

  http://www.asp.net/mvc/tutorials/older-versions/controllers-and-routing/asp-net-mvc-routing-overview-cs

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章