[翻译]ASP.NET MVC 3 开发的20个秘诀(十九)[20 Recipes for Programming MVC 3]:路由用户至特定的Controller或Action
议题
在今天投巨资转战各个搜索引擎霸主地位的战役中,像下面这个网站地址,想赢得比赛是相当困难的:
http://www.example.com/books/details?id=4
使用Routes转换之后,地址看起来是这样的:
http://www.example.com/20-recipes-for-mvc3
这个URL地址,为用户和搜索引擎提供了更多的内容。
解决方案
使用RouteCollectionExtensions类的MapRoute功能来生成更友好的名称来显示内容,而不是使用数字标识。
讨论
路由信息可以在Web.config或Global.asax.cs中设置。在Web.config文件中添加System.Web.Routing程序集引用,并在Global.asax.cs文件中创建一个默认的路由机制,去适配所有的控制器和动作。因此,当添加“BooksController”控制器之后,可以不使用扩展名,而通过“/Books”的URL,就像访问普通ASP.NET站点一样访问。
下面的秘诀将会展示几种不同的设置路由的方式。第一种方式是允许使用书籍标题作为网站链接。例如,有本书名为“20 Recipes for Programming MVC 3”就可以使用如下网址直接访问:
http://localhost/20 Recipes for Programming MVC 3
而不是当前解决方案中更为复杂的URL,如:
http://localhost/Books/Details?id=1
要使用这个技术方案,首先要在MVC项目中打开Global.asax.cs。当网页第一次加载的时候,会在Application_Start()中调用RegisterRoutes()方法创建一个默认路由,下面的这个例子包含创建新的MapRoute的功能。修改RegisterRoutes方法如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using MvcApplication4.Models;
using System.Data.Entity;
using System.Globalization;
using System.Threading;
namespace MvcApplication4
{
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方法接受了四个参数:
- 路由名称,当前是“BookName”;
- URL参数名称,在这里是“{Keyword}”,这个是变量名称,稍后会使用到;
- 这个参数要定义默认的控制器,动作以及附加参数;在这个例子中,默认控制器为“Books”,默认动作为“Index”;
- 这个是URL的约定参数(例如:变量);在这个例子中,将之前提到的变量“keyword”传递给BooksController中的Index动作。
上述这个路由还可以结合之前做过的BooksController中的搜索的例子,如果只找到一个结果,则直接显示详细信息页面。这位用户提供了一种输入方式,在域名输入之后,用户输入了图书标题或关键字。如果只有一个返回结果,用户将看到这本书;否则,用户将看到与她们输入的关键字相关的搜索结果。
在接下来的例子中,通过继承RouteBase类,将会创建更为复杂的路由功能,实现通过书的标题来定义网站子域名的功能。例如,http://mvc3book.localhost/将返回“20 Recipes for Programming MVC 3”的书籍详情页面。
为了实现这个功能,必须修改“Book”模型类,添加新成员“ShortName”。通过创建继承RouteBase类的派生类来实现将书名做为子域名的参数来搜索书籍。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using MvcApplication4.Validations;
namespace MvcApplication4.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”。在下面的类中,将从HttpContextRequest.Headers对象中获取当前域名,使用“.”字符将域名分割为字符串数组。执行错误检查,以确保当前子域名不是“www”。然后获取子域名在图书的“ShrotName”字段中查找特定图书,如果找到该图书,则创建RouteData对象,然后添加Books控制器、Index动作以及该图书的ID,并返回RouteData对象。如果没有找到,则显示该页面。在下面的这个例子中很容易就可以将错误页面的例子改为“Books/Index”的关键字搜索页面(如上例所示)。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Routing;
using System.Web.Mvc;
using MvcApplication4.Models;
namespace MvcApplication4.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 MvcApplication4.Models;
using System.Data.Entity;
using System.Globalization;
using System.Threading;
using MvcApplication4.Utils;
namespace MvcApplication4
{
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 },
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);
}
}
}
}
这个例子是一个非常好的设计体系,使用这种设计方案可以很容易的创建其他的路由,例如,显示某用户的个人资料页或修改多语言版本的秘诀,允许使用路由的方式通过URL切换不同语言版本,例如:en.example.com或en.example.com。
参考