§8 URLs and Routing
Before ASP.NET MVC, the core assumption of routing in ASP.NET (just like in many other web application platforms) was that URLs correspond directly to files on the server’s hard disk. The server executes and serves the page or file corresponding to the incoming URL. Table 8–1 gives an example
这样, 限制会很多, 有的人不想让别人知道自己文件的路径, 或者是这些表示方式太难堪了等等.
§8.1 Putting the Programmer Back in Control
ASP.NET MVC打破了这种局面, since ASP.NET MVC’s requests are handled by controller classes (compiled into a .NET assembly), there are no particular files corresponding to incoming URLs.所以这也就没有路径对应的特定文件了.
You are given complete control of your URL schema—that is, the set of URLs that are accepted and their mappings to controllers and actions. 下面我们来看看mvc中是如何定义路径的.
This is all managed by the framework’s routing system.这完全是又框架的路径系统管理的.
§8.1.1 About Routing and Its .NET Assemblies
The routing system was originally designed for ASP.NET MVC, but it was always intended to be shared with other ASP.NET technologies, including Web Forms.路径系统本来是给mvc自己用的, 但是也会被其他asp.net技术使用. 所以路径代码是放在一个独立的程序集里(System.Web.Routing.dll in .NET 3.5, and simply System.Web.dll in .NET 4),而不是在System.Web.Mvc.dll 中.
§8.2 Setting Up Routes
我们来看看路径的配置, 在global.asax.cs文件里
public class MvcApplication : System.Web.HttpApplication { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( "Default", // 路由名称 "{controller}/{action}/{id}", // 带有参数的 URL new { controller = "Home", action = "Index", id = UrlParameter.Optional } // 参数默认值 ); } protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterRoutes(RouteTable.Routes); } }
When the application first starts up (i.e., when Application_Start() runs), the RegisterRoutes() method populates a global static RouteCollection object called RouteTable.Routes. That’s where the application’s routing configuration lives. The most important code is that shown in bold: MapRoute() adds an entry to the routing configuration. To understand what it does a little more clearly, you should know that this call to MapRoute() is just a concise alternative to writing the following:当应用程序启动的时候,也就是Application_Start() 鱼腥的时候, RegisterRoutes() 静态方法会装入一个叫做RouteTable.Routes的全局静态RouteCollection对象. 也是放置路径配置的地方.MapRoute是路径配置的入口, 为了简单的说明, 我们来举下面的例子
Route myRoute = new Route("{controller}/{action}/{id}", new MvcRouteHandler()) { Defaults = new RouteValueDictionary(new { controller = "Home", action = "Index", id = UrlParameter.Optional }) }; routes.Add("Default", myRoute);
我们上面的看到的这段代码和系统自动生成的那段代码是同义的.
§8.2.1 Understanding the Routing Mechanism
The routing mechanism runs early in the framework’s request processing pipeline. Its job is to take an incoming URL and use it to obtain an IHttpHandler object that will handle the request. 早起的路由机制运行在框架的请求处理管道. 它的工作就是使用进来的URL, 并用它来获取一个能够处理请求的的IHttpHandler 对象
Many newcomers to the MVC Framework struggle with routing. It isn’t comparable to anything in earlier ASP.NET technologies, and it’s easy to configure wrong. By understanding its inner workings, you’ll avoid these difficulties, and you’ll also be able to extend the mechanism powerfully to add extra behaviors across your whole application.许多mvc框架的新手对路由的概念改到很困惑. 因为它和以往任何的asp.net技术不同,而且很容易配置错误. 通过了解它的内部运作,我们就可以避免这些问题,你也可以通过增加额外的行为使整个应用程序拓展的更强大.
The Main Characters: RouteBase, Route, and RouteCollection
路由配置主要有3个部分:
- RouteBase is the abstract base class for a routing entry. You can implement unusual routing behaviors by deriving a custom type from it (I’ve included an example near the end of this chapter), but for now you can forget about it.
- Route is the standard, commonly used subclass of RouteBase that brings in the notions of URL templating, defaults, and constraints. This is what you’ll see in most examples.
- A RouteCollection is a complete routing configuration. It’s an ordered list of RouteBase-derived objects (e.g., Route objects).
How Routing Fits into the Request Processing Pipeline
When a URL is requested, the system invokes each of the IHttpModules registered for the application. 当一个URL被请求, 系统调用每个在应用程序中已经注册的IHttpModules .其中一个就是UrlRoutingModule
The Order of Your Route Entries Is Important
If there’s one golden rule of routing, this is it: put more-specific route entries before less-specific ones. 如果有个路由配置的黄金规则: 那么就是将特殊路径放在一般路径的前面. 因为系统的对路由的算法是从最顶端开始, 而不是找最适合的.
§8.2.2 Adding a Route Entry
默认的路由是很普通的, 如果你想要处理其他类型的URL, 你还需要做一些手脚. 我来举个简单的例子, 比如我们想要用URL /Catalog来查看这个系列的所有产品
routes.Add(new Route("Catalog", new MvcRouteHandler()) { Defaults = new RouteValueDictionary( new { controller = "Products", action = "List" } ) });我们可以用上面的这段代码来实现我们的目的. 它可以帮助我们实现/Catalog 或者是 /Catalog?some=querystring, 但是 /Catalog/Anythingelse 这样的url是不行的.
URL Patterns Match the Path Portion of a URL
Meet RouteValueDictionary
A different technique to populate a RouteValueDictionary is to supply an IDictionary<string, object> as a constructor parameter, or alternatively to use a collection initializer, as in the following example:
routes.Add(new Route("Catalog", new MvcRouteHandler()) { Defaults = new RouteValueDictionary { { "controller", "Products" }, { "action", "List" } } });Take a Shortcut with MapRoute()
ASP.NET MVC adds an extension method to RouteCollection, called MapRoute(). 你会发现这比使用routes.Add(new Route(...)) 方便很多.
routes.MapRoute("PublicProductsList", "Catalog", new { controller = "Products", action = "List" });In this case, PublicProductsList is the name of the route entry. It’s just an arbitrary unique string. That’s optional.
§8.2.3 Using Parameters
As you’ve seen several times already, parameters can be accepted via a curly brace syntax. 正像你前面看到的, 参数可以放在{}里, 这里我们加一个color参数到路由中:
routes.MapRoute(null, "category/{color}", new { controller = "Products", action = "List" });This route will now match URLs such as /Catalog/yellow or /Catalog/1234, and the routing system will add a corresponding name/value pair to the request’s RouteData object. On a request to /Catalog/yellow, for example, RouteData.Values["color"] would be given the value yellow.
Receiving Parameter Values in Action Methods
You know that action methods can take parameters. When ASP.NET MVC wants to call one of your action methods, it needs to supply a value for each method parameter. One of the places where it can get values is the RouteData collection. It will look in RouteData’s Values dictionary, aiming to find a key/value pair whose name matches the parameter name.
我们知道action方法可以带参数. 当mvc想要调用一个action方法, 它需要提供一个value给方法的参数. 它获得value的一个地方就是RouteData collection. 它会在RouteData’s 的键值对中寻找一个和参数名对应的value.
So, if you have an action method like the following, its color parameter would be populated according to the {color} segment parsed from the incoming URL:所以, 如果你有个action方法像下面这样的, 那么它的color参数就在传入的url中的{color}中
public ActionResult List(string color) { // Do something }To be more precise, action method parameters aren’t simply taken directly from RouteData.Values, but instead are fetched via the model binding system, which is capable of instantiating and supplying objects of any .NET type, including arrays and collections. You’ll learn more about this mechanism in Chapters 9 and 12. 更准确的说, action方法的参数不仅仅只是简单的直接从RouteData.Values获取. 而是从模型绑定系统中获取,各种.net类型. 你会在第9章和12章中了解更多.
§8.2.4 Using Defaults
You didn’t give a default value for {color}, so it became a mandatory parameter. The Route entry no longer matches a request for /Catalog. You can make the parameter optional by adding to your Defaults object: 在上面的例子中, 我们没有给{color}一个默认值, 它变成了一个强制的参数. 路由入口不再匹配/Catalog 请求. 你可以
routes.MapRoute(null, "Catalog/{color}", new { controller = "Products", action = "List", color = (string)null });这样, 路由就能匹配/Category和/Category/orange了.
如果你想要一个非null 的默认值, 比如没有null的 int, 你可以显式的指定值
routes.Add(new Route("Catalog/{color}", new MvcRouteHandler()) { Defaults = new RouteValueDictionary( new { controller = "Products", action = "List", color = "Beige", page = 1 } ) });That’s a perfectly fine thing to do; it’s the correct way to set up RouteData values that are actually fixed for a given Route entry. For example, for this Route object, RouteData["controller"] will always equal "Products", regardless of the incoming URL, so matching requests will always be handled by ProductsController.
这样,不管输入的url是什么, 匹配请求总会被ProductsController处理.
Remember that when you use MvcRouteHandler (as you do by default in ASP.NET MVC), you must have a value called controller; otherwise, the framework won’t know what to do with the incoming request and will throw an error. The controller value can come from a curly brace parameter in the URL, or can just be specified in the Defaults object, but it cannot be omitted.
Creating Optional Parameters with No Default Value
就像默认的路由配置, 我们可以指定默认值UrlParameter.Optional.
routes.MapRoute(null, "Catalog/{page}", new { controller = "Products", action = "List", page = UrlParameter.Optional });这样, 当访问的URL有page值的时候, 我们就采用传入的vallue, 如果没有, 那么我们就不想action方法中传任何参数.你可能会疑惑, 为什么不用0或者是null 作为默认参数, 下面是它的两个原因:
- If your action method takes a page parameter of type int, then because that type can’t hold null, you would have to supply the default value of 0 or some other int value. This means the action method would now always receive a legal value for page, so you wouldn’t be able to control the default value using the MVC Framework’s [DefaultValue] attribute or C# 4’s optional parameter syntax on the action method itself (you’ll learn more about these in the next chapter).
- 如果你的action方法有个int类型的page参数,但是它是值类型, 不能是null. 所以你需要提供一个默认值(0或者是其他的值). 这也以为着action方法总是需要一个合法的值, 所以, 如果action方法自身使用mvc框架的[defaultvalue]特性或者是C#4的可选参数语法, 你将无法控制它的类型.(你会在接下来的一章中了解更多)
- Even if your action’s page parameter was nullable, there’s a further limitation. When binding incoming data to action method parameters, the MVC Framework prioritizes routing parameter values above query string values (you’ll learn more about value providers and model binding in Chapter 12). So, any routing value for page—even if it’s null—would take priority and hide any query string value called page.
- 即时你的action的page参数可以是null类型. 这里还有个限制. 当action方法的参数是binding类型的时候, mvc 框架会将路由参数优先于查询字符串值.(你会在12章中学到值提供者和模型绑定). 所以, 任何为page设置的路由值--即使是null--也会优先于访问page的查询字符串
UrlParameter.Optional解决了这两个问题
§8.2.5 Using Constraints
有时候, 你会想要添加额外的条件, 以匹配特定的route. 比如:
- 你想匹配get请求, 而不是post请求
- 一些参数要匹配特定的参数(e.g. ID参数必须匹配数字类型)
- 有的route用来匹配常规的web浏览器发来的请求, 有的匹配iphone发来的同样URL
In these cases, you’ll use the Route’s Constraints property
Matching Against Regular Expressions
为了保证参数是数字类型的, 我们使用这样的规则:
routes.MapRoute(null, "Articles/{id}", new { controller = "Articles", action = "Show" }, new { id = @"\d{1,6}" });这样, route就会匹配 /Articles/1 和 /Articles/123456 这两种类型, 而不是其他的,(这里的正则表达式表示的是: 数字类型,1~6个)
Matching HTTP Methods
If you want your Route to match only GET requests (not POST requests), you can use the built-in HttpMethodConstraint class (it implements IRouteConstraint)—for example:
routes.MapRoute(null, "Articles/{id}", new { controller = "Articles", action = "Show" }, new { httpMethod = new HttpMethodConstraint("GET") });你想匹配什么样的HTTP方法, 就把它放到HttpMethodConstraint构造器中, 比如,new HttpMethodConstraint("GET", "DELETE").
你要注意的是 HttpMethodConstraint 和 [HttpGet] and [HttpPost] 无关
Matching Custom Constraints
如果前面的几种都不能满足你, 那么你还是可以实现它的. 举个例子,如果你想建立一个只允许web浏览器进入的路由入口, 你可以创建如下的约束:
public class UserAgentConstraint : IRouteConstraint { private string _requiredSubstring; public UserAgentConstraint(string requiredSubstring) { this._requiredSubstring = requiredSubstring; } public bool Match(HttpContextBase httpContext, Route route, string paramName, RouteValueDictionary values, RouteDirection routeDirection) { if (httpContext.Request.UserAgent == null) return false; return httpContext.Request.UserAgent.Contains(_requiredSubstring); } }下面的路由只能匹配由iphone发起的请求:
routes.Add(new Route("Articles/{id}", new MvcRouteHandler()){ Defaults = new RouteValueDictionary(new { controller = "Articles", action = "Show" }), Constraints = new RouteValueDictionary( new { id = @"\d{1,6}", userAgent = new UserAgentConstraint("iPhone") }});§8.2.6 Prioritizing Controllers by Namespace
§8.2.7 Accepting a Variable-Length List of Parameters
§8.2.8 Matching Files on the Server’s Hard Disk
§8.2.9 Using IgnoreRoute to Bypass the Routing System
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{filename}.xyz"); // Rest of routing config goes here }Here, {filename}.xyz is treated as a URL pattern just like in a normal route entry, so in this example,
the routing system will now ignore any requests for /blah.xyz or /foo.xyz?some=querystring. (Of course,
you must place this entry higher in the route table than any other entry that would match and handle
those URLs.) You can also pass a constraints parameter if you want tighter control over exactly which
URLs are ignored by routing.这里{filename}.xyz 被当作一个URL模型, 就像一个普通的路由入口.