【譯】MVC3 20個秘方-(19)URL—其實我更想懂你:路由用戶到特定的Controller和Action
問題
當今如此對搜索引擎霸主的爭奪戰是如此激烈,像下邊這樣的網站地址很難在這場比賽中獲勝:http://www.example.com/books/details?id=4
使用路由,網站可以變成這樣:
http://www.example.com/20-recipes-for-mvc3
無論是對用戶還是搜索引擎,這將提供更多的語境。
解決方案
使用RouteCollectionExtensions 類下的MapRoute 函數去生成更友好的名字去展示內容而不是數字ID。
討論
在MVC中可以通過Web.config和Global.asax.cs文件設置路由。在web.config中包含System.Web.Routing程序集并且在Global.asax.cs中使用它去對在它其中的所有controller和action創建一個默認的路由機制。因此在添加BooksController的時候,可以通過/Books 訪問不帶擴展名的 URL,就像在ASP.NET 官網那樣。
接下來的秘方將演示設立幾個不同的有用的技術去創建路由。
第一個路由將允許網站直接連接到book的title上。例如,有一本書叫MVC3的20個秘方,它可以通過http://localhost/20 Recipes for Programming MVC 3這個地址被直接訪問。然而當前的解決方案就需要一個更復雜的URL就像:http://localhost/Books/Details?id=1。要開始創建這個路由,打開在MVC project里的Global.asax.cs文件。在RegisterRoutes()函數里創建里了一個默認的路由。在第一次加載網站的時候Application_Start()函數會調用它。下邊的例子包含一個更新的RegisterRoutes 函數,添加了一個新的路由到MapRoute函數中:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using MvcApplication.Models;
using System.Data.Entity;
using System.Globalization;
using System.Threading;
namespace MvcApplication
{
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterGlobalFilters(
GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"BookName", // Route name
"{Keyword}", // URL with parameters
new
{
controller = "Books",
action = "Index",
id = UrlParameter.Optional
},
new { Keyword = "\\w+" });
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}",
// URL with parameters
new
{
controller = "Home",
action = "Index",
id = UrlParameter.Optional
}
);
}
protected void Application_Start()
{
Database.SetInitializer<BookDBContext>(
new BookInitializer());
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
protected void Application_AcquireRequestState(
object sender, EventArgs e)
{
if (HttpContext.Current.Session != null)
{
CultureInfo ci =
(CultureInfo)this.Session["CurrentLanguage"];
if (ci == null)
{
ci = new CultureInfo("en");
this.Session["CurrentLanguage"] = ci;
}
Thread.CurrentThread.CurrentUICulture = ci;
Thread.CurrentThread.CurrentCulture =
CultureInfo.CreateSpecificCulture(ci.Name);
}
}
}
}
在上邊的例子里,MapRoute 函數接收4個參數。
- route name,在這里是BookName。
- 附帶任何參數的URL。在這里是{Keyword},這是可變的,一會兒會用到。
- 這個參數默認的是controller ,action和任何附加的變量。在這個例子里,默認的controller是Books 并且Action是Index
- 他包含(例如,變量)對于URL。在這里,前邊提到的Keyword變量傳遞到BooksController的Index action上。
當搜索關鍵字時,他可以在URL的域名后邊輸入一個書名或關鍵字,如果僅僅返回了一個結果,用戶將被重定向到詳細信息頁面,并看到那本書。否則用戶將看到一個根據他關鍵字的搜索結果。在下一個例子里。一個新的路由將被創建。它將口占RouteBase類。均需一個更復雜的路由。將用一個子域名替代在域名后邊輸入書名。例如 http://mvc3book.localhost/ 將返回上述圖書的詳細內容-MVC3編程的20個秘方。
為了讓這成為可能,BOOK model 需要被更新去包含一個新的參數,名為“ShortName”。 此參數將被用來作為子域,并允許書籍通過創建擴展的RouteBase類的類進行搜索。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using MvcApplication.Validations;
namespace MvcApplication.Models
{
public class Book
{
public int ID { get; set; }
[Required]
public string ShortName { get; set; }
[Required]
[Display(Name = "TitleDisplay", ResourceType = typeof(Resources.Resource1))]
public string Title { get; set; }
[Display(Name = "IsbnDisplay", ResourceType = typeof(Resources.Resource1))]
[Required]
[IsbnValidation]
public string Isbn { get; set; }
[Display(Name = "SummaryDisplay", ResourceType = typeof(Resources.Resource1))]
[Required]
public string Summary { get; set; }
[Display(Name = "AuthorDisplay", ResourceType = typeof(Resources.Resource1))]
[Required]
public string Author { get; set; }
[Display(Name = "ThumbnailDisplay", ResourceType = typeof(Resources.Resource1))]
public string Thumbnail { get; set; }
[Display(Name = "PriceDisplay", ResourceType = typeof(Resources.Resource1))]
[Range(1, 100)]
public double Price { get; set; }
[Display(Name = "PublishedDisplay", ResourceType = typeof(Resources.Resource1))]
[DataType(DataType.Date)]
[Required]
public DateTime Published { get; set; }
}
}
現在必須創建一個新的類將包含新的路由背后的邏輯。選擇Utils文件夾中,右鍵單擊并選擇“添加→類。這個新的類將被稱為BookDomainRoute.cs。下面的類將從Request.Headers為當前HttpContext檢索域名。該域名將被.”操作符的分成“數組。執行一些錯誤檢查以確保我們有一個子域名不是WWW。
第一塊子域,例如,ShortName,是用來執行書本上表的搜索,找到特定書籍。如果查找到了書籍,創建一個新的對象類RouteData,設置Controller為Books,Action 設置為Detail,最后的ID是這本書的ID。如果沒有找到書籍,主頁將顯示出來。在下面的例子,它可以很容易改變以直接導航用戶到一個錯誤頁或根據Keyword 跳轉到Books/index 頁(在前面的例子)。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Routing;
using System.Web.Mvc;
using MvcApplication.Models;
namespace MvcApplication.Utils
{
public class BookDomainRoute : RouteBase
{
private BookDBContext db = new BookDBContext();
public override RouteData GetRouteData(HttpContextBase httpContext)
{
// Get the domain name
var url = httpContext.Request.Url.Authority;
// Split into array of parts
var pieces = url.Split('.');
// Ensure there is a subdomain and it's not www
if (pieces.Length < 2 && pieces[0] != "www")
{
return null;
}
string ShortName = pieces[0];
// Find the book by ShortName
var books = from b in db.Books select b;
books = books.Where(b => b.ShortName.ToUpper().Contains(ShortName.ToUpper()));
// Check to make sure a book was found
if (books.Count() == 0)
{
return null;
}
// Get the first result
Book book = books.First();
// Set the route data
RouteData routeData = new RouteData(this, new MvcRouteHandler());
routeData.Values.Add("controller", "Books");
routeData.Values.Add("action", "Details");
routeData.Values.Add("id", book.ID);
return routeData;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
return null;
}
}
}
最后Global.asax.cs文件必須再次更新,包括新創建的路由。為了使新的路由類可以找到。需要添加using語句到utils目錄。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using MvcApplication.Models;
using System.Data.Entity;
using System.Globalization;
using System.Threading;
using MvcApplication.Utils;
namespace MvcApplication
{
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=9394801
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add(new BookDomainRoute());
routes.MapRoute(
"BookName", // Route name
"{Keyword}", // URL with parameters
new { controller = "Books", action = "Index", id = UrlParameter.Optional }, // Parameter defaults
new { Keyword = "\\w+" });
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
protected void Application_Start()
{
Database.SetInitializer<BookDBContext>(new BookInitializer());
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
String connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["BooksDBContext"].ConnectionString;
System.Web.Caching.SqlCacheDependencyAdmin.EnableNotifications(connectionString);
System.Web.Caching.SqlCacheDependencyAdmin.EnableTableForNotifications(connectionString, "Books");
}
protected void Application_AcquireRequestState(object sender, EventArgs e)
{
if (HttpContext.Current.Session != null)
{
CultureInfo ci = (CultureInfo)this.Session["CurrentLanguage"];
if (ci == null)
{
ci = new CultureInfo("en");
this.Session["CurrentLanguage"] = ci;
}
Thread.CurrentThread.CurrentUICulture = ci;
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(ci.Name);
}
}
}
}
上述的例子包含良好的使用路由的偉大的開始。兩者都可以很容易地更新執行其他路由,例如,子域名可以用來顯示用戶的特定的個人資料頁,或以前實施的多語種秘方可更新為使用一個路由類允許象en.example.com或fr.example.com一樣的URL設置當前的語言文化。
另請參見
RouteCollectionExtension, RouteData

浙公網安備 33010602011771號