第11章 URL、路由及区域 — 精通MVC 3 框架

URLs, Routing, and Areas
URL、路由、与区域

Before the introduction ofMVCASP.NET assumed that there was a direct relationship between requestedURLs and the files on the server hard disk. The job of the server was toreceive the request from the browser and deliver the output from thecorresponding file, as follows:
在引入MVC之前,ASP.NET假设在请求的URL与服务器硬盘上的文件之间有直接的关系服务器的工作是接收浏览器的请求并递送相应文件的输出,如下所示:

Request URL
请求URL

Corresponding File
相应文件

http://mysite.com/default.aspx

e:\webroot\default.aspx

http://mysite.com/admin/login.aspx

e:\webroot\admin\login.aspx

http://mysite.com/articles/AnnualReview

File not found! Send error 404.
文件未找到!发送404错误

This approach works justfine for Web Forms, where each ASPX page is both a file and a self-containedresponse to a request. It doesn’t make sense for an MVC application, whererequests are processed by action methods in controller classes, and there is noone-to-one correlation to the files on the disk.
这种方式对Web表单工作得很好,在这里,每一个ASPX页面既是一个文件,又是一个对请求自包含的响应。这对MVC应用程序没什么意义,在这里,请求是由控制器类中的动作方法处理的,而且与硬盘上的文件没有一对一的相互关系。

To handle MVC URLs, theASP.NET platform uses the routing system. In this chapter, we’ll show you howto set up and use the routing system to create powerful and flexible URLhandling for your projects. As you’ll see, the routing system lets you createany pattern of URLs you desire, and express them in a clear and concise manner.
为了处理MVC的网址,ASP.NET平台使用了路由系统。在本章中,我们将向你演示,如何建立和使用路由系统,以便为你的项目生成功能强大而灵活的URL处理。正如你将看到的,路由系统可以让你生成你所希望的任何URL模式,并且以清晰而简洁的方式表达他们。

Introducing the Routing System
路由系统介绍

The routing system has twofunctions:
路由系统有两个功能

  • Examine an incoming URL 1100.210 and figureout for which controller and action the request is intended. As you mightexpect, this is what we want the routing system to do when we receive a clientrequest.
    检查一个输入请求(1100.210应当是误输 — 译者注),并想象出该请求想要的是哪个控制器和动作。正如你所期望的,在我们接收到一个客户端请求时,这就是我们希望路由系统去做的事情。
  • Generate outgoing URLs. These are the URLs that appear in the HTMLrendered from our views so that a specific action will be invoked when the userclicks the link (at which point, it has become an incoming URL again).
    生成输出URL。这些是我们的视图渲染的HTML中出现的URL,以便用户点击这些连接时调用一个特定的动作(这时,它已经再次变成了输入URL)。

In the first part of thischapter, we will focus on defining routes and using them to process incomingURLs so that the user can reach your controllers and actions. Then we’ll showyou how to use those same routes to generate the outgoing URLs you will need toinclude in your HTML.
在本章的第一部分,我们将关注定义路由,并使用它们去处理输入URL,以使用户能够到达你的控制器和动作。然后,我们将向你演示,如何使用那些同样的路由,来生成你需要包含在你的HTML中的输出URL。


THE ROUTING SYSTEM ASSEMBLY
路由系统程序集


Although the routingsystem is needed by the ASP.NET MVC Framework, it is intended to be used withother ASP.NET technologies as well, including Web Forms. Because of this, therouting system classes are in the System.Web assembly and not inSystem.Web.Mvc.
虽然路由系统是ASP.NETMVC框架所需要的,但也期望它被其它ASP.NET技术所使用,包括Web表单。由于这一原因,路由系统类在System.Web程序集中,而不是在System.Web.Mvc中。

When you create a new MVCapplication, you will see that Visual Studio has added a reference to theSystem.Web.Routing assembly. This is a holdover from support for .NET 3.5 andhas no effect on your project. You can delete this reference if you wish.
当你生成一个新MVC应用程序时,你将看到Visual Studio已经添加了一个对System.Web.Routing程序集的引用。这是一个延期支持.NET 3.5的程序集(意即在.NET 4.0中仍用它对.NET 3.5进行支持,但对.NET 4.0的应用程序可以不用它 — 译者注),而且对你的项目没有影响。如果你愿意,你可以删除这个引用。

We focus onusing routing with the MVC Framework, but much of the information applies whenusing routing with other parts of the ASP.NET platform. Adam has included a lotof information about using routing with the base ASP.NET platforms and with WebForms in his book Applied ASP.NET 4 inContext.
我们关注于与MVC框架一起使用路由,但大量的信息,在与ASP.NET平台的其它部分使用路由时,也是适用的。Adam在他的《Applied ASP.NET 4 in Context》一书中包含了大量关于ASP.NET基础平台和Web表单使用路由的信息。


Creating the Routing Project
生成路由项目

To demonstrate the routingsystem, we need a project to which we can add routes. We have created a new MVCapplication using the Internet Application template, and we called the projectUrlsAndRoutes. We selected this template because it gives us some ready-madecontrollers and actions.
为了演示路由系统,我们需要一个能够对之添加路由的项目。我们用Internet应用程序模板生成了一个新MVC应用程序,此项目名为UrlsAndRoutes。我们选择这个模板是因为它有一些现成的控制器和动作。

Routes are defined inGlobal.asax. If you open this file in Visual Studio, you will see that routestake up quite a lot of this (admittedly short) file. Listing 11-1 shows theGlobal.asax from our project, which we have edited slightly to make it morereadable.
路由是在Global.asax中定义的。如果你在Visual Studio中打开这个文件,你将看到路由占据了这个文件的很大一部分(诚然,这还是短的)。清单11-1显示了我们项目的Global.asax,我们对它作了点编辑,以使它更可读。


n Note Strictly speaking, routes are defined in Global.asax.cs,which is the code-behind file for Global.asax. When you double-clickGlobal.asax in the Solution Explorer window, Visual Studio actually opensGlobal.asax.cs. For that reason, we are going to refer to both filescollectively as Global.asax.
注:严格地说,路由是在Global.asax.cs中定义的,这是Global.asax的后台代码文件(有些文章或书籍中也叫隐藏代码文件 —译者注)。当你在解决方案窗口中双击Global.asax时,Visual Studio实际上打开Global.asax.cs。出于这一原因,我们把这两个文件都称为Global.asax。


Listing 11-1. The DefaultGlobal.asax.cs File

using System.Web.Mvc; using System.Web.Routing; namespace UrlsAndRoutes { publicclass MvcApplication : System.Web.HttpApplication { protectedvoid Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); } public static voidRegisterRoutes(RouteCollection routes) { ... routes are defined here... } publicstatic void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(newHandleErrorAttribute()); } } } 

The Application_Startmethod is called by the underlying ASP.NET platform when the application is firststarted, which leads to the RegisterRoutes method being called. The parameterto this method is the value of the static RouteTable.Routes property, which isan instance of the RouteCollection class.
Application_Start方法是由当前ASP.NET平台在应用程序启动时首先调用的,它导致RegisterRoutes方法被调用。送给这个方法的参数是静态的RouteTable.Routes属性的值,这是RouteCollection类的一个实例。

We have deleted the routesthat are added by default from the RegisterRoutes method because we want toshow you the various techniques for creating routes and the different kinds ofroutes that are available. Before we can do that though, we need to take a stepback and look at something that is central to the routing system: URL patterns.
我们已经从RegisterRoutes方法中删除了被默认添加的路由,因为我们想给你演示生成路由的各种技术,以及可用路由的不同种类。在我们能够做这些事情之前,我们需要退一步,先考察一些路由系统核心的东西:URL模式。

Introducing URL Patterns
介绍URL模式

The routing system worksits magic using a set of routes. These routes collectively compose the URLschema or scheme for an application, which is the set of URLs that yourapplication will recognize and respond to.
路由系统用一组路由来实现它的魔法。这些路由共同组成了应用程序的URL模式或方案,这个URL模式(或方案)是你的应用程序能够认识并能对之作出响应的一组URL。

We don’t need to manuallytype out all of the individual URLs we are willing to support. Instead, eachroute contains a URL pattern, which is compared to an incoming URL. If thepattern matches the URL, then it is used by the routing system to process thatURL.
我们不需要手工键入我们打算支持的所有个别的URL。而是,每条路由都含有一个URL模式,用它与一个输入URL进行比较。如果该模式与这个URL匹配,那么它(URL模式)便被路由系统用来对这个URL进行处理。

(上两段的含义可以这样理解:路由系统由若干条路由所组成;每条路由都有一个URL模式;一个URL模式相当于表示URL的一个公式,有若干URL与这个模式相匹配;应用程序中要定义的就是与URL模式相关的路由;对一个请求进行服务时,路由系统查看这个URL与哪个URL模式相匹配,便用这个模式对应的路由对这个URL进行处理。有点绕口,但仔细阅读应该能理解。 — 译者注)

Let’s start with anexample URL from the SportsStore application:
让我们从SportsStore应用程序的URL例子开始:

http://mysite.com/Admin/Index

This is the URL that weused to access the administrator’s view of the product catalog. If you referback to Chapter 9, you will see that this URL points to the Index action methodin the AdminController class.
这是我们用来访问产品目录的管理员视图的URL。如果你回过头去参考第9章,你会看到这个URL指向了AdminController类中的Index动作方法。

URLs can be broken downinto segments. These are the parts of the URL, excluding the hostname and querystring, that are separated by the / character. In the example URL, there are twosegments, as shown in Figure 11-1.
URL可以被分解成片段。它们是这个URL除主机名和查询字串以外的、以/字符分隔的各个部分。在这个例子URL中,有两个片段,如图11-1所示。

图11-1

Figure 11-1. Thesegments in an example URL
图11-1. 示例URL的片段

The first segment containsthe word Admin, and the second segment contains the word Index. To the humaneye, it is obvious that the first segment relates to the controller and thesecond segment relates to the action. But, of course, we need to express thisrelationship in a way that the routing system can understand. Here is a URLpattern that does this:
第一段含有单词Admin,而第二段含有单词Index。很显然,第一片段关系到控制器,第二段关系到动作。但是当然,我们需要以一种路由系统可以理解的方式来表示这种关系。以下是做这件事的一个URL模式:

{controller}/{action}

When processing anincoming URL, the job of the routing system is to match the URL to a pattern,and then extract values from the URL for the segment variables defined in thepattern. The segment variables are expressed using braces (the { and }characters). The example pattern has two segment variables with the namescontroller and action.
当处理一个输入URL时,路由系统的工作是把这个URL与一个模式匹配,然后为这个模式中定义的片段变量从这个URL中提取值。片段变量用花括号({和}字符)表示。这个示例模式有名字为controller和action的两个片段变量。

We say match to a pattern,because an MVC application will usually have several routes, and the routingsystem will compare the incoming URL to the URL pattern of each route until itcan find a match.
我们说,与一个模式匹配,因为一个MVC应用程序通常会有几个路由,而路由系统会把输入URL与每个路由的URL模式相比较,直到找到一条匹配的。


n Note The routing system doesn’t have any special knowledge ofcontrollers and actions. It just extracts values for the segment variables andpasses them along the request pipeline. It is later in the pipeline, when therequest reaches the MVC Framework proper, that meaning is assigned to thecontroller and action variables. This is why the routing system can be usedwith Web Forms and how we are able to create our own variables.
注:路由系统没有任何控制器和动作的专门知识。它只是为片段变量提取值,并沿请求管道传递它们(注意,管道中传递的是 —译者注)。在管道之后,当请求恰好到达MVC框架之时,其含意(指)被赋给controller和action变量。这是为什么路由系统可以被用于Web表单的原因,以及我们能够如何生成我们自己的变量。


By default, a URL patternwill match any URL that has the correct number of segments. For example, theexample pattern will match any URL that has two segments, as illustrated byTable 11-1.
默认地,一个URL模式将匹配有正确片段数的任何URL。例如,例如,上例的模式将匹配任何有两个片段的URL,如表11-1所示。

Table 11-1. Matching URLs

Request URL
请求URL

Segment Variables
片段变量

http://mysite.com/Admin/Index

controller = Admin

action = Index

http://mysite.com/Index/Admin

controller = Index

action = Admin

http://mysite.com/Apples/Oranges

controller = Apples

action = Oranges

http://mysite.com/Admin

No match—too few segments
不匹配 — 片段太少

http://mysite.com/Admin/Index/Soccer

No match—too many segments
不匹配 — 片段太多

Table 11-1 highlights twokey behaviors of URL patterns:
表11-1高亮了URL模式的两个关键行为:

  • URL patterns are conservative, and will match only URLs that havethe same number of segments as the pattern. You can see this in the fourth andfifth examples in the table.
    URL模式是保守的,而且只匹配与模式具有相同片段数的URL。你可以在表中的第四、第五个例子看到这种情况。
  • URL patterns are liberal. If a URL does have the correct number ofsegments, the pattern will extract the value for the segment variable, whateverit might be.
    URL模式是宽松的。如果一个URL正好具有正确的片段数,该模式就会提取片段变量的值,而不管它可能是什么。

These are the defaultbehaviors, which are the keys to understanding how URL patterns function.You’ll see how to change the defaults later in this chapter.
这些是默认行为,这是理解URL模式如何运行的关键。你将在本章稍后看到如何修改这种默认行为。

As we mentioned, therouting system doesn’t know anything about an MVC application, and so URLpatterns will match even when there is no controller or action that correspondsto the values extracted from a URL. You can see this demonstrated in the secondexample in Table 11-1. We have transposed the Admin and Index segments in theURL, and so the values extracted from the URL have also been transposed.
正如我们提到过的,路由系统并不知道关于MVC应用程序的任何情况,因此,即使在没有控制器或动作与从一个URL提取出来的值相符时,路由系统也会进行匹配。你可以通过表11-1的第二个例子明白这种情况的演示。我们在URL中传递了Admin和Index片段,因此,从这个URL提取的值也已经被传递(路由系统并不管是否有相应的控制器和动作方法— 译者注)。

Creating and Registering a Simple Route
生成并注册一个简单的路由

Once you have a URLpattern in mind, you can use it to define a route. Listing 11-2 shows how tocreate a route using the example URL pattern from the previous section in theRegisterRoutes method of Global.asax.
一旦你在头脑中已经有了一个URL模式,你就可以用它来定义一个路由。清单11-2演示了,如何用上一小节的URL模式示例,在Global.asax的RegisterRoutes方法中生成一条路由。

Listing 11-2. Registeringa Route

public static void RegisterRoutes(RouteCollectionroutes) { Route myRoute = newRoute("{controller}/{action}", new MvcRouteHandler()); routes.Add("MyRoute", myRoute); } 

We create a new routeobject, passing in our URL pattern as a constructor parameter. We also pass inan instance of MvcRouteHandler. Different ASP.NET technologies providedifferent classes to tailor the routing behavior, and this is the class we usefor ASP.NET MVC applications. Once we have created the route, we add it to theRouteCollection object using the Add method, passing in the name we want theroute to be known by and the route we have created.
我们生成了一个新的路由对象,传递了我们的URL模式作为构造器参数。我们也传递了MvcRouteHandler的一个实例。不同的ASP.NET技术提供了不同的类来设计路由的行为,而这个(指MvcRouteHandler类 — 译者注)是我们用于ASP.NET MVC应用程序的类。一旦我们已经生成了这个路由,我们就可以用Add方法把它添加到RouteCollection对象,传递我们给这个路由所起的名字,和我们已经生成的这个路由。


n Tip Naming your routes is optional, and there is an argument thatdoing so sacrifices some of the clean separation of concerns that otherwisecomes from routing. We are pretty relaxed about naming, but we explain why thiscan be a problem in the “Generating a URL from a Specific Route” section later in thischapter.
提示:命名你的路由是可选的,而且有一种争论,这样做牺牲了路由其它方面的一些关注分离(这句可能没译好— 译者注)。我们对命名是相当宽松的,但我们会在本章稍后的“从指定路由生成一个URL”小节中,解释为什么这可能是一个问题


A more convenient way ofregistering routes is to use the MapRoute method defined in the RouteCollectionclass. Listing 11-3 shows how we can use this method to register our route.
注册路由的一个更方便的办法是使用定义在RouteCollection中的MapRoute方法。清单11-3演示了我们如何才能用这个方法来注册路由。

Listing 11-3. Registeringa Route Using the MapRoute Method

public static void RegisterRoutes(RouteCollectionroutes) { routes.MapRoute("MyRoute","{controller}/{action}"); } 

This approach is slightlymore compact, mainly because we don’t need to create an instance of theMvcRouteHandler class. The MapRoute method is solely for use with MVCapplications. ASP.NET Web Forms applications can use the MapPageRoute method,also defined in the RouteCollection class.
这个办法要更紧凑一点,主要是因为我们不需要生成MvcRouteHandler的一个实例。MapRoute方法专用于MVC应用程序。ASP.NET的Web表单应用程序可以用MapPageRoute方法,它也是在RouteConllection类中定义的。


UNIT TEST: TESTING INCOMING URLS
单元测试:测试输入路由


We recommend that you unittest your routes to make sure they process incoming URLs as expected, even ifyou choose not to unit test the rest of your application. URL schemas can getpretty complex in large applications, and it is easy to create something thathas unexpected results.
即使你对应用程序的其余部门不采用单元测试,我们也建议你单元测试路由,以确保它们按预期的来处理输入URL。在大型应用程序中,URL方案可能会相当复杂,很容易生成一些意外的结果。

In previous chapters, wehave avoided creating common helper methods to be shared among tests in orderto keep each unit test description self-contained. For this chapter, we aretaking a different approach. Testing the routing schema for an application ismost readily done when you can batch several tests in a single method, and thisbecomes much easier with some helper methods.
在上一章中,为了保持单元测试描述是自包含的,我们已经避免生成在测试之间共享的通用辅助方法。对于本章,我们要采用不同的办法。当你能够在一个单一的方法中批处理几个测试时,测试一个应用程序的路由方案是最容易完成的,而且利用某些辅助方法,这会变得容易很多。

To test routes, we need tomock three classes: HttpRequestBase, HttpContextBase, and HttpResponseBase(this last class is required for testing outgoing URLs, which we cover later inthis chapter). Together, these classes re-create enough of the MVCinfrastructure to support the routing system.
为了测试路由,我们需要模仿三个类:HttpRequestBase、HttpContextBase、和HttpResponseBase(最后一个是测试输出URL所需要的,我们会在本章稍后涉及它)。合在一起,这些类重新生成了足以支持路由系统的MVC架构

Here is the helper methodthat creates the mock objects, which we added to our unit test project:
以下是生成这个模仿对象的辅助方法,我们把它加到了我们的测试项目:

private HttpContextBase CreateHttpContext(stringtargetUrl = null, string httpMethod ="GET") { // create the mock request Mock<HttpRequestBase> mockRequest = newMock<HttpRequestBase>(); mockRequest.Setup(m =>m.AppRelativeCurrentExecutionFilePath).Returns(targetUrl); mockRequest.Setup(m =>m.HttpMethod).Returns(httpMethod); // create the mock response Mock<HttpResponseBase> mockResponse =new Mock<HttpResponseBase>(); mockResponse.Setup(m =>m.ApplyAppPathModifier( It.IsAny<string>())).Returns<string>(s=> s); // create the mock context, using the requestand response Mock<HttpContextBase> mockContext = newMock<HttpContextBase>(); mockContext.Setup(m =>m.Request).Returns(mockRequest.Object); mockContext.Setup(m =>m.Response).Returns(mockResponse.Object); // return the mocked context return mockContext.Object; } 

The setup here isrelatively simple. We expose the URL we want to test through theAppRelativeCurrentExecutionFilePath property of the HttpRequestBase class, andexpose the HttpRequestBase through the Request property of the mockHttpContextBase class. Our next helper method lets us test a route:
这里的setup相对简单。我们通过HttpRequestBase类的AppRelativeCurrentExecutionFilePath属性暴露了我们想要测试的URL,并且通过HttpContextBase类的Request属性暴露了HttpRequestBase。我们的下一个方法让我们测试一个路由:

private void TestRouteMatch(string url, stringcontroller, string action, object routeProperties = null, string httpMethod ="GET") { //Arrange RouteCollectionroutes = new RouteCollection(); MvcApplication.RegisterRoutes(routes); // Act -process the route RouteDataresult = routes.GetRouteData(CreateHttpContext(url, httpMethod)); // Assert Assert.IsNotNull(result); Assert.IsTrue(TestIncomingRouteResult(result,controller, action, routeProperties)); } 

The parameters of thismethod let us specify the URL to test, the expected values for the controllerand action segment variables, and an object that contains the expected valuesfor any additional variables we have defined. We’ll show you how to create suchvariables later in the chapter. We also defined a parameter for the HTTPmethod, which we’ll explain in the “Constraining Routes” section.
该方法的参数让我们指定要测试的URL、controller和action片段变量所期望的值,以及一个对象,它含有为我们已经定义的附加变量所期望的值。我们在本章稍后将给你演示如何生成这种变量。我们也为HTTP方法定义了一个参数,我们将在“强制路由”小节解释它。

The TestRouteMatch methodrelies on another method, TestIncomingRouteResult, to compare the resultobtained from the routing system with the segment variable values we expect.This method uses .NET reflection so that we can use an anonymous type toexpress any additional segment variables. Don’t worry if this method doesn’tmake sense, as this is just to make testing more convenient; it isn’t arequirement for understanding MVC. Here is the TestIncomingRouteResult method:
TestRouteMatch方法依赖于另一个方法,TestIncomingRouteResult,以比较从路由系统获得的结果和我们期望的分段变量的值。这个方法使用了.NET反射,以使我们能够使用一个匿名类型来表示任何附加的分段变量。如果对这个方法不理解,不用着急,因为其作用只是让测试更方便:它不是理解MVC所需要的。以下是这个TestIncomingRouteResult方法:

private bool TestIncomingRouteResult(RouteDatarouteResult, string controller, string action, object propertySet = null) { Func<object, object, bool> valCompare =(v1, v2) => { returnStringComparer.InvariantCultureIgnoreCase.Compare(v1, v2) == 0; }; bool result =valCompare(routeResult.Values["controller"], controller) &&valCompare(routeResult.Values["action"], action); if (propertySet != null) { PropertyInfo[] propInfo =propertySet.GetType().GetProperties(); foreach (PropertyInfo pi in propInfo) { if(!(routeResult.Values.ContainsKey(pi.Name) &&valCompare(routeResult.Values[pi.Name], pi.GetValue(propertySet, null)))) { result = false; break; } } } return result; } 

We also need a method tocheck that a URL doesn’t work. As you’ll see, this can be an important part ofdefining a URL schema.
我们也需要一个方法来检查一个URL不工作。正如你将看到的,这可能是定义一个URL方案的重要部分。

private void TestRouteFail(string url) { // Arrange RouteCollection routes = newRouteCollection(); MvcApplication.RegisterRoutes(routes); // Act - process the route RouteData result = routes.GetRouteData(CreateHttpContext(url)); //Assert Assert.IsTrue(result ==null || result.Route == null); } 

TestRouteMatch andTestRouteFail contain calls to the Assert method, which throws an exception ifthe assertion fails. Since C# exceptions are propagated up the call stack, wecan create simple test methods that can test a set of URLs and get the testbehavior we require. Here is a test method that tests the route we defined inListing 11-3:
TestRouteMatch和TestRouteFail包含了对Assert方法的调用,如果断言失败,它会弹出一个异常。因为C#异常被上传到调用堆栈,我们可以生成简单的测试方法,它能够测试一组URL,并得到我们需要的测试行为。以下是一个我们用来测试清单11-3中路由的测试方法:

[TestMethod] public void TestIncomingRoutes() { // checkfor the URL that we hope to receive TestRouteMatch("~/Admin/Index","Admin", "Index"); // checkthat the values are being obtained from the segments TestRouteMatch("~/One/Two","One", "Two"); //ensure that too many or too few segments fails to match TestRouteFail("~/Admin/Index/Segment"); TestRouteFail("~/Admin"); } 

This test uses theTestRouteMatch method to check the URL we are expecting and also checks a URLin the same format to make sure that the controller and action values are beingobtained properly using the URL segments. We also use the TestRouteFail methodto make sure that our application won’t accept URLs that have a differentnumber of segments. When testing, we must prefix the URL with the tilde (~)character, because this is how the ASP.NET Framework presents the URL to therouting system.
这个测试用TestRouteMatch方法来检查我们期望的URL。而且也检查一个同样格式的URL,以确保用URL分段恰当地获得controller和action的值。我们也用TestRouteFail方法来确保我们的应用程序不接收有不同分段数目的URL。当进行测试时,我们必须用波浪线(~)字符作为URL的前缀,因为这是ASP.NET框架如何把URL表现给路由系统的。

Notice that wedidn’t need to define the routes in the test methods. This is because we areloading them directly from the RegisterRoutes method in the Global.asax class.
注意,我们不需要在测试方法中定义路由。这是因为我们从Global.asax类中的RegisterRoutes方法中直接装载它们。


You can see the effect ofthe route we have created by starting the application. When the browserrequests the default URL (http://localhost:<port>/), the application willreturn a 404 – Not Found response. This is because we have not yet created aroute for this URL, and just support the format {controller}/{action}. To testthis kind of URL, navigate to ~/Home/Index. You can see the result that theapplication generates in Figure 11-2.
你可以通过启动应用程序看到我们已经定义的路由的结果。当浏览器请求默认URL(http://localhost:<端口>),应用程序将返回一个“404 — 未找到”响应。这是因为我们还没有生成用于这个URL的路由,只支持{controller}/{action}格式。为了测试这种URL,导航到~/Home/Index。你可以看到应用程序生成了如图11-2所示的结果。

图11-2

Figure 11-2. Manuallytesting a URL pattern

图11-2. 手工测试URL模式

Our URL pattern hasprocessed the URL and extracted a value for the controller variable of Home andfor the action variable Index. The MVC Framework maps this request to the Indexmethod of the Home controller, which was created for us when we selected theInternet Application MVC project template.
我们的URL模式已经处理了这个URL,并为controller变量提取了Home值,为action变量提取了Index值。MVC框架把这个请求映射到了Home控制器的Index方法,这是在我们选择Internet应用程序的MVC项目模板时,VS为我们已经生成的。

And so you have createdyour first route and used it to process an incoming URL. In the followingsections, we’ll show you how to create more complex routes, giving you richerand more flexible URL schemas for your MVC applications.
至此,你已经生成了你的第一个路由,并用它来处理一个输入URL。在以下小节中,我们给你演示如何生成更复杂的路由,给出用于MVC应用程序的更丰富和更灵活的URL方案。

Defining Default Values
定义默认值

The reason that we got anerror when we requested the default URL for the application is that it didn’tmatch the route we had defined. The default URL is expressed as ~/to therouting system, and so there are no segments that can be matched to thecontroller and action variables.
当我们请求应用程序的默认URL时,出现错误的原因是它不匹配我们已经定义的路由。默认URL被表示成~/送给路由系统,因此,没有与controller和action变量匹配的片段。

We explained earlier thatURL patterns are conservative, in that they will match only URLs with thespecified number of segments. We also said that this was the default behavior.One way to change this behavior is to use default values. A default value isapplied when the URL doesn’t contain a segment that can be matched to thevalue. Listing 11-4 provides an example of a route that contains a defaultvalue.
前面我们曾解释过,URL模式是保守的,它们只匹配指定片段数的URL。我们也说过,这是默认行为。修改这种行为的一个办法是使用默认值。当URL不含与一个值匹配的片段时,用默认值。清单11-4提供了一个含有默认值的路由的例子。

Listing 11-4. Providing aDefault Value in a Route

public static void RegisterRoutes(RouteCollectionroutes) { routes.MapRoute("MyRoute","{controller}/{action}", new { action = "Index" }); } 

Default values aresupplied as properties in an anonymous type. In Listing 11-4, we have provideda default value of Index for the action variable. This route will match alltwo-segment URLs, as it did previously. For example, if the URLhttp://mydomain.com/Home/Index is requested, the route will extract Home as thevalue for the controller and Index as the value for the action.
默认值是作为匿名类型的属性来提供的。在清单11-4中,我们已经为action变量提供了一个Index的默认值。这个路由将匹配所有两片段的URL,就像它之前所做的那样。例如,如果请求http://mydomain.com/Home/Index,该路由将为controller提取Home值,为action提取Index值。

Now that we have provideda default value for the action segment, the route will also match single-segmentURLs as well. When processing the URL, the routing system will extract thecontroller value from the sole URL segment, and use the default value for theaction variable. In this way, we can request the URL http://mydomain.com/Homeand invoke the Index action method on the Home controller.
现在,我们已经为action片段提供了一个默认值,该路由也将匹配单片段URL匹配。当处理URL时,路由系统将从这个单片段URL提取controller值,并为action变量这个默认值。这样,我们可以请求http://mydomain.com/Home,并调用Home控制器上的Index动作方法。

We can go further anddefine URLs that don’t contain any segment variables at all, relying on justthe default values to identify the action and controller. We can map thedefault URL using default values for both, as shown in Listing 11-5.
我们可以继续向前,并定义根本不含任何片段变量的URL,只依靠默认值来标识controller和action。我们可以用默认值为这两者映射默认URL,如清单11-5所示。

Listing 11-5. ProvidingAction and Controller Default Values in a Route

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

By providing defaultvalues for both the controller and action variables, we have created a routethat will match URLs that have zero, one, or two segments, as shown in Table11-2.
通过为controller和action变量提供默认值,我们已经生成了与零个、一个、或两个片段的ULR匹配的路由,如表11-2所示。

Table 11-2. Matching URLs

Number of Segment

Example

Maps To

0

mydomain.com

controller = Home

action = Index

1

mydomain.com/Customer

controller = Customer

action = Index

2

mydomain.com/Customer/List

controller = Customer

action = List

3

mydomain.com/Customer/List/All

No match—too many segments

The fewer segments wereceive in the incoming URL, the more we rely on the default values. If we runthe application again, the browser will request the default URL once more, butthis time our new route will take effect and add our default values for thecontroller and action, allowing the incoming URL to be mapped to the Indexaction in the Home controller, as shown in Figure 11-3.
我们在输入URL中接收的片段越少,我们依靠的默认值越多。如果我们再次运行此应用程序,浏览器将再次请求默认URL,但这次我们的新路由将起作用了,并为controller和action添加默认值,允许这个输入URL被映射到Home控制器中的Index动作,如图11-3所示。

图11-3

Figure 11-3. Adding aroute for the default URL
图11-3. 添加一条用于默认URL的路由


UNIT TESTING: DEFAULT VALUES
单元测试:默认值


We don’t need to take anyspecial actions if we use our helper methods to test routes that define defaultvalues. For example, here is a simple test for the route in Listing 11-5:
如果我们用我们的辅助方法来测试定义默认值的路由,我们不需要采取任何特殊的动作。例如,以下是对清单11-5路由的简单测试:

[TestMethod] public void TestIncomingRoutes() { TestRouteMatch("~/","Home", "Index"); TestRouteMatch("~/Customer","Customer", "Index"); TestRouteMatch("~/Customer/List","Customer", "List"); TestRouteFail("~/Customer/List/All"); } 

The only pointof note is that we must specify the default URL as ~/, since this is howASP.NET presents the URL to the routing system. If we specify the empty string("") that we used to define the route or /, the routing system willthrow an exception, and the test will fail.
需要说明的一点是,我们必须把默认URL指定为~/,因为这是ASP.NET如何把URL表示给路由系统的。如果我们用空字符串(””)或/来定义路由,路由系统将弹出一个异常,而测试将会失败。

Using Static URL Segments
使用静态URL片段

Not all of the segments ina URL pattern need to be variables. You can also create patterns that havestatic segments. Suppose we want to match a URL like this to support URLs thatare prefixed with Public:
并不是一个URL模式中的所有片段都需要是可变的。你也可以生成具有静态片段的模式。假如我们想匹配像这样的URL,以支持带有前缀Public的URL:

http://mydomain.com/Public/Home/Index

We can do so by using apattern like the one shown in Listing 11-6.
我们可以通过使用像清单11-6所示的模式来做这件事。

Listing 11-6. A URLPattern with Static Segments

public static void RegisterRoutes(RouteCollectionroutes) { routes.MapRoute("MyRoute","{controller}/{action}", new { controller = "Home", action ="Index" }); routes.MapRoute("","Public/{controller}/{action}", new {controller = "Home", action = "Index" }); } 

This URL pattern willmatch only URLs that contain three segments, the first of which must be Customers. The other twosegments can contain any value, and will be used for the controller and actionvariables.
这个URL模式将只匹配含有三个片段的URL,第一个必须是Public(原文为Customers,不对 — 译者注)。其它两个片段可以含有任何值,并将被用于controller和action变量。

We can also create URLpatterns that have segments containing both static and variable elements, suchas the one shown in Listing 11-7.
我们也可以生成既有静态也有可变元素的片段的URL模式,比如,清单11-7所表示的这种。

Listing 11-7. A URLPattern with a Mixed Segment

public static void RegisterRoutes(RouteCollectionroutes) { routes.MapRoute("","X{controller}/{action}"); routes.MapRoute("MyRoute","{controller}/{action}", new{ controller = "Home", action = "Index" }); routes.MapRoute("","Public/{controller}/{action}", new { controller = "Home", action= "Index" }); } 

The pattern in this routematches any two-segment URL where the first segment starts with the letter X.The value for controller is taken from the first segment, excluding the X. Theaction value is taken from the second segment. As an example, the following URLwould be matched by the route:
这条路由中的模式匹配任意两片段URL,而第一个片段以字母X打头。用于controller的值取自第一个片段,除X以外。action的值取自第二个片段。作为一个例子,以下URL与这条路由匹配:

http://mydomain.com/XHome/Index

This URL would be directedto the Index action method on the Home controller.
这个URL被指向Home控制器上的Index动作方法。


ROUTE ORDERING
路由排序


In Listing 11-6, wedefined a new route and placed it before all of the others in theRegisterRoutes method. We did this because routes are applied in the order inwhich they appear in the RouteCollection object. The MapRoute method adds aroute to the end of the collection, which means that routes are generallyapplied in the order in which we add them. We say “generally” because there aremethods that let us insert routes in specific locations. We tend not to usethese methods, because having routes applied in the order in which they aredefined makes understanding the routing for an application simpler.
在清单11-6中,我们定义了一条新路由,并把它放在RegisterRoutes方法中的所有其它路由之前。我们这么做,是因为路由是以它们在RouteCollection对象中出现的顺序被运用的。MapRoute方法把一条路由添加到该集合的末端,意即,路由一般地是按我们添加他们的顺序被运用的。我们说“一般地”,是因为有办法让我们把路由插入到指定位置。我们不倾向于使用这种办法,因为让路由以它们被定义的顺序来运用,更容易理解用于一个应用程序的路由。

The route system tries tomatch an incoming URL against the URL pattern of the route that was definedfirst, and proceeds to the next route only if there is no match. The routes aretried in sequence until a match is found or the set of routes has beenexhausted. The result of this is that we must define more specific routesfirst. The route we added in Listing 11-7 is more specific than the route thatfollows. Suppose we reversed the order of the routes, like this:
路由系统试图根据首先被定义的路由模式来匹配一个输入URL,并且只在不匹配时才会处理下一条路由。路由依次被试,直到找到匹配的一条,或这组路由被试完。其结果是,我们必须首先定义比较特殊的路由。我们在清单11-7中添加的路由要比以下路由更特殊。假设我们颠倒路由的顺序,像这样:

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

Then the first route,which matches any URL with zero, one, or two segments, will be the one that isused. The more specific route, which is now second in the list, will never bereached. The new route excludes the leading X of a URL, but this won’t be doneby the older route, Therefore, a URL such as this:
那么,第一条路由,它匹配任何具有零、一、二片段的URL,将是被使用的一条。更特殊的路由,现在是该清单的第二条,将是不可到达的。这条新路由去除一条URL的前导X,但旧路由(指第一条 译者注)却做不到,因此,像这样的一条URL:

http://mydomain.com/XHome/Index

will be targeted to acontroller called XHome, which doesn’t exist, and so will lead to a 404 – NotFound error being sent to the user.
将以名为XHome的控制器为目标,而这是不存在的,因此,将导致一个“404 — 未找到”错误被发送给用户。

If you have notread the section on unit testing incoming URLs, we suggest you do so now. Ifyou unit test only one part of your MVC application, it should be your URLschema.
如果你还没有阅读单元测试输入URL这一小节,我们建议你现在就读。如果你只对你应用程序的一个部分做单元测试,这应该就是你的URL方案。


We can combine static URLsegments and default values to create an alias for a specific URL. This can beuseful if you have published your URL schema publicly and it forms a contractwith your user. If you refactor an application in this situation, you need topreserve the previous URL format. Let’s imagine that we used to have acontroller called Shop, which has now been replaced by the Home controller.Listing 11-8 shows how we can create a route to preserve the old URL schema.
我们可以结合静态片段和默认值为特定的路由生成一个别名。如果你已经公开地发布了你的URL方案,并且它与你的用户形成了标准合同,那末,这可能是用有的。如果你在这种情况下重构你的应用程序,你需要保留以前的URL格式。让我们设想,我们以前用一个Shop控制器,现在要由Home控制器来替代。清单11-8演示了我们如何才能生成一个保留旧式URL方案的路由。

Listing 11- 8. MixingStatic URL Segments and Default Values

public static void RegisterRoutes(RouteCollectionroutes) { routes.MapRoute("ShopSchema","Shop/{action}", new { controller = "Home" }); ...otherroutes... } 

The route we have addedmatches any two-segment URL where the first segment is Shop. The action valueis taken from the second URL segment. The URL pattern doesn’t contain avariable segment for controller, so the default value we have supplied is used.This means that a request for an action on the Shop controller is translated toa request for the Home controller. And we can go one step further and createaliases for action methods that have been refactored away as well and are nolonger present in the controller. To do this, we simply create a static URL andprovide the controller and action values as defaults, as shown in Listing 11-9.
我们添加的路由匹配任何两片段的URL,第一个片段是Shop。cation的值取自第二个URL片段。这个URL模式不含对控制器的可变片段,因而使用我们所提供的默认值。这意味着对Shop控制器上一个动作的请求被转换成对Home控制器的请求。而且,我们可以更进一步,生成动作方法的别名,它也被重构,且不再出现在控制器中。为此,我们简单地生成一个静态URL,并给controller和action提供默认值,如清单11-9所示。

Listing 11-9. Aliasing aController and an Action

public static void RegisterRoutes(RouteCollectionroutes) { routes.MapRoute("ShopSchema2","Shop/OldAction", new {controller = "Home", action = "Index" }); routes.MapRoute("ShopSchema", "Shop/{action}", new {controller = "Home" }); ...other routes... } 

Notice, once again, thatwe have placed our new route so that it is defined first. This is because it ismore specific than the routes that follow. If a request for Shop/OldAction wereprocessed by the next defined route, for example, we would get a differentresult from the one we want. The request would be dealt with using a 404 – NotFound error, rather than being translated in order to preserve a contract withour clients.
一次注意,我们放置新路由的位置,以使它先被定义。这是因为它比后面的路由更特殊。例如,如果一个对Shop/OlaAction的请求被下一条路由定义来处理,我们就会得到与我们想要的不同的结果。这个请求将被处理成一个“404 — 未找到”错误,而不是被转换以保持与我们客户的标准合同。


UNIT TEST: TESTING STATIC SEGMENTS
单元测试:测试静态片段


Once again, we can use ourhelper methods to routes whose URL patterns contain static segments.
再一次地,我们可以把我们的辅助方法用于其URL模式含有静态片段的路由。

Here is an example thattests the route added in Listing 11-8:
以下是测试清单11-8中所添加的路由的例子:

[TestMethod] public void TestIncomingRoutes() { TestRouteMatch("~/Shop/Index","Home", "Index"); } 

Defining Custom Segment Variables
定义自定义片段变量

We are not limited to justthe controller and action variables. We can also define our own variables, asshown in Listing 11-10.
我们并不受限于controller和action变量。我们也可以定义自己的变量,如清单11-10所示。

Listing 11-10. DefiningAdditional Variables in a URL Pattern

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

The route’s URL patterndefines the typical controller and action variables, as well as a customvariable called id. This route will match any zero-to-three-segment URL. Thecontents of the third segment will be assigned to the id variable, and if thereis no third segment, the default value will be used.
该路由的URL模式定义了典型的controller和action变量,以及一个名为id的自定义变量。这条路由将匹配任何0-3个片段的URL。第三个片段的内容将被赋给id变量,而如果没有第三个片段,将采用默认值。


n Caution Some names are reserved and not available for customsegment variable names. These are controller, action, and area. The meaning ofthe first two are obvious, and we will explain the role of areas in the“Working with Areas” section later in this chapter.
注意:有些名字是保留的,因而对自定义片段变量名是非法的。这些是controller、action、和area。前两个的含义是显然的,我们将在本章稍后的“与区域一起工作”小节中解释区域(area)的作用。


We can access any of thesegment variables in an action method by using the RouteData.Values property.To demonstrate this, we have added a method to the HomeController class calledCustomVariable, as shown in Listing 11-11.
我们可以通过使用RouteData.Values属性,在一个动作方法中访问任何一个片段变量。为了对此演示,我们把一个叫做CustomVariable的动作方法添加到HomeController类,如清单11-11所示。

Listing 11-11. Accessinga Custom Segment Variable in an Action Method

public ViewResult CustomVariable() { ViewBag.CustomVariable= RouteData.Values["id"]; returnView(); } 

This method obtains thevalue of the custom variable in the route URL pattern and passes it to the viewusing the ViewBag. Listing 11-12 shows the corresponding view for the method,CustomVariable.cshtml, which we have placed in the Views/Home folder.
这个方法获取路由的URL模式中的自定义变量的值,并用ViewBag把它传递给视图。清单11-12显示了该方法的相应视图,CustomVariable.cshtml,我们把它放在了/Views/Home文件夹中。

Listing 11-12. Displayingthe Value of a Custom Segment Variable

@{ ViewBag.Title = "CustomVariable"; } <h2>Variable:@ViewBag.CustomVariable</h2> 

If you run the applicationand navigate to the URL /Home/CustomVariable/Hello, the CustomVariable actionmethod in the Home controller is called, and the value of the custom segmentvariable is retrieved from the ViewBag and displayed, as shown in Figure 11-4.
如果你运行应用程序,并导航到/Home/CustomVariable/Hello网址,Home控制器中的CustomVaiable动作方法就会被调用,自定义片段变量的值就会通过ViewBag被接收并被显示出来,如图产11-4所示。

图11-4

Figure 11-4.Displaying the value of a custom segment variable
图11-4. 显示自定义片段变量的值


UNIT TEST: TESTING CUSTOM SEGMENTVARIABLES
单元测试:测试自定义片段变量


We included support for testingcustom segment variables in our test helper methods. The TestRouteMatch methodhas an optional parameter that accepts an anonymous type containing the namesof the properties we want to test for and the values we expect. Here is amethod that tests the route defined in Listing 11-10:
在我们的测试辅助方法中,我们包括了对测试自定义片段变量的支持。TestRouteMatch方法有一个可选的参数,它接受一个匿名类型,该匿名类型含有我们想要测试的属性名和我们期望的值。以下是一个测试方法,它测试在清单11-10中定义的路由:

[TestMethod] public void TestIncomingRoutes() { TestRouteMatch("~/","Home", "Index", new {id = "DefaultId"}); TestRouteMatch("~/Customer","Customer", "index", new { id = "DefaultId" }); TestRouteMatch("~/Customer/List","Customer", "List", new { id = "DefaultId" }); TestRouteMatch("~/Customer/List/All","Customer", "List", new { id = "All" }); TestRouteFail("~/Customer/List/All/Delete"); } 

Using Custom Variables as Action Method Parameters
用自定义变量作为动作方法参数

Using the RouteData.Valuesproperty is only one way to access custom route variables. The other way ismuch more elegant. If we define parameters to our action method with names thatmatch the URL pattern variables, the MVC Framework will pass the valuesobtained from the URL as parameters to the action method. For example, thecustom variable we defined in the route in Listing 11-8 is called id. We canmodify the CustomVariable action method so that it has a matching parameter, asshown in Listing 11-13.
使用RouteData.Values属性只是访问自定义路由变量的一种办法。另一种办法要优雅得多。如果我们用匹配URL模式的变量名定义动作方法的参数,MVC框架将把从URL获得的这个值作为参数传递给该动作方法。例如,我们在清单11-8中定义的名为id的自定义变量。我们可以修改CustomVariable动作方法,以使它具有一个匹配的参数,如清单11-13所示。

Listing 11-13. Mapping aCustom URL Segment Variable to an Action Method Parameter

public ViewResult CustomVariable(string id) { ViewBag.CustomVariable= index; returnView(); } 

When the routing systemmatches a URL against the URL we defined in Listing 11-8, the value of thethird segment in the URL is assigned to the custom variable index. The MVCFramework compares the list of segment variables with the list of action methodparameters, and if the names match, passes the values from the URL to themethod.
当路由系统根据我们在清单11-8中所定义的URL来匹配一个URL时,URL中第三片段的值被传递给自定义变量index。MVC框架比较片段变量列表和动作方法参数,如果名字匹配,便把URL的值传递给该动作方法。

We have defined the idparameter as a string, but the MVC Framework will try to convert the URL valueto whatever parameter type we define. If we declared the id parameter as an intor a DateTime, then we would receive the value from the URL parsed to aninstance of that type. This is an elegant and useful feature that removes theneed for us to handle the conversion ourselves.
我们已经把id参数定义为一个字符串,但MVC框架会试图把RUL的值转换成我们所定义的任何参数类型。如果我们把id参数声明为一个int或一个DateTime,那么我们从URL模式接收到的值将被解析成该类型的一个实例。这是一个雅致而有用的特性,它去除了我们自己处理转换的需要。


n Note The MVC Framework uses the model binding system to convertthe values contained in the URL to .NET types, and can handle much more complexsituations than shown in this example. We cover model binding in Chapter 17.
注:MVC框架采用模型绑定系统来把包含在URL中的值转换成.NET类型,并且能够处理比这个例子复杂得多的情况。我们将在第17章涉及模型绑定。


Defining Optional URL Segments
定义可选URL片段

An optional URL segment isone that the user does not need to specify, but for which no default value isspecified. Listing 11-14 shows an example. We specify that a segment variableis optional by setting the default value to UrlParameter.Optional, as shown inbold in the listing.
可选URL片段是指用户不需要指定,但又未指定默认值的片段。清单11-14显示了一个例子。我们通过把默认值设置为UrlParameter.Optional的办法把一个片段变量指定为可选的,如清单中的黑体所示。

Listing 11-14. Specifyingan Optional URL Segment

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

This route will match URLswhether or not the id segment has been supplied. Table 11-3 shows how thisworks for different URLs.
这条路由将匹配不管是否提供id的URL。表11-3显示了对不同的URL,它是如何工作的。

Table 11-3. Matching URLswith an Optional Segment Variable

Number of Segment

Example URL

Maps To

0

mydomain.com

controller = Home

action = Index

1

mydomain.com/Customer

controller = Customer

action = Index

2

mydomain.com/Customer/List

controller = Customer

action = List

3

mydomain.com/Customer/List/All

controller = Customer

action = List

id = All

4

mydomain.com/Customer/List/All/Delete

No match—too many segments

As you can see from thetable, the id variable is added to the set of variables only when there is a correspondingsegment in the incoming URL. To be clear, it is not that the value of id isnull when no corresponding segment is supplied; rather, the case is that an idvariable is not defined.
正如你从上表所看到的,id变量只有当输入URL中存在相应片段时,才会被添加到变量集中。需要明确的是,当没有提供相应的片段时,id的值不是null,而是此时id变量未定义。

This feature is useful ifyou need to know whether the user supplied a value. If we had supplied adefault value for the id parameter and received that value in the actionmethod, we would be unable to tell if the default value was used or the userjust happened to request a URL that contained the default value.
如果你需要知道用户是否提供了一个值,这一特性是有用的。如果我们对id参数提供一个默认值,并在动作方法中接收这个值,我们就说不出是否有这个默认值,还是用户只是偶然地请求了一个含有这个默认值的URL。

A common use for optionalsegments is to enforce the separation of concerns, so that default values foraction method parameters are not contained in the routing definitions. If youwant to follow this practice, you can use the C# optional parameters feature todefine your action method parameters, as shown in Listing 11-15.
可选片段的一个通常的运用是强迫关注分离,以使动作方法参数的默认值不包含在路由定义中。如果你想实践一下,你可以用C#可选参数特性来定义你的动作方法参数,如清单1-15所示。

Listing 11-15. Defining aDefault Value for an Action Method Parameter

public ViewResult CustomVariable(string id = "DefaultId") { ViewBag.CustomVariable = id; return View(); } 

UNIT TESTING: OPTIONAL URL SEGMENTS
单元测试:可选URL片段


The only issue to be awareof when testing optional URL segments is that the segment variable will not beadded to the RouteData.Values collection unless a value was found in the URL.This means that you should not include the variable in the anonymous typeunless you are testing a URL that contains the optional segment. Here is a testmethod for the route defined in Listing 11-14.
当测试可选URL片段时,唯一要意识到的问题是,片段变量不会被添加到RouteData.Values集合,除非在URL中发现有一个值。这意味着,你不应该把这个变量包括在匿名类型中,除非你是在测试一个含有可选片段的URL。以下是对清单11-14所定义路由的一个测试方法。

[TestMethod] public void TestIncomingRoutes() { TestRouteMatch("~/","Home", "Index"); TestRouteMatch("~/Customer","Customer", "index"); TestRouteMatch("~/Customer/List","Customer", "List"); TestRouteMatch("~/Customer/List/All","Customer", "List", new { id = "All" }); TestRouteFail("~/Customer/List/All/Delete"); } 

Defining Variable-Length Routes
定义可变长路由

Another way of changingthe default conservatism of URL patterns is to accept a variable number of URLsegments. This allows you to route URLs of arbitrary lengths in a single route.You define support for variable segments by designating one of the segmentvariables as a catchall, done by prefixing it with an asterisk (*), as shown inListing 11-16.
改变URL模式默认保守性的另一个办法是,接收一个可变数目的URL片段。这允许你在一个单一的路由中路由任意长度的URL。通过设计一个叫做catchall(全匹配)的片段变量,以星号(*)为其前缀,你可以定义对可变片段的支持,如清单11-16所示。(所谓“可变长”是指片段数可变 —译者注)

Listing 11-16.Designating a Catchall Variable

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

We have extended the routefrom the previous example to add a catchall segment variable, which is weimaginatively called catchall. This route will now match any URL. The firstthree segments are used to set values for the controller, action, and idvariables, respectively. If the URL contains additional segments, they are allassigned to the catchall variable, as shown in Table 11-4.
我们已经通过上例添加一个catchall片段变量扩展了路由,我们形象地称之为全匹配。现在这个路由将匹配任何URL。前三个片段用于分别设置controller、action、和id变量的值。如果URL含有更多片段,它们都被赋给catchall变量,如表11-4所示。

Table 11-4. Matching URLswith a Catchall Segment Variable

Number of Segments

Example URL

Maps To

0

mydomain.com

controller = Home

action = Index

1

mydomain.com/Customer

controller = Customer

action = Index

2

mydomain.com/Customer/List

controller = Customer

action = List

3

mydomain.com/Customer/List/All

controller = Customer

action = List

id = All

4

mydomain.com/Customer/List/All/Delete

controller = Customer

action = List

id = All

catchall = Delete

5

mydomain.com/Customer/List/All/Delete/Perm

controller = Customer

action = List

id = All

catchall = Delete/Perm

There is no upper limit tothe number of segments that the URL pattern in this route will match. Noticethat the segments captured by the catchall are presented in the formsegment/segment/segment. We are responsible for processing the string to breakout the individual segments.
这个路由中的URL模式匹配的片段数目没有上限。注意,由catchall捕获的片段是以“片段/片段/片段”的形式表示的。我们有责任对这个字符串进行处理,把它分解成一个个片段。


UNIT TEST: TESTING CATCHALL SEGMENTVARIABLES
单元测试:测试catchall片段变量


We can treat a catchallvariable just like a custom variable. The only difference is that we must expectmultiple segments to be concatenated in a single value, such assegment/segment/segment. Notice that we will not receive the leading ortrailing / character. Here is a method that demonstrates testing for a catchallsegment, using the route defined in Listing 11-16 and the URLs shown in Table11-4:
我们可以像对待一个自定义变量一样来处理一个catchall变量。唯一的差别是,我们必须期望多个片段被连接成一个单一的字符串值,如,片段/片段/片段。注意,我们不会接收到前导或后导的/字符。以下是一个演示对catchall片段进行测试的方法,采用清单11-16中所定义的路由以及表11-4中所演示的URL:

[TestMethod] public void TestIncomingRoutes() { TestRouteMatch("~/", "Home","Index"); TestRouteMatch("~/Customer","Customer", "Index"); TestRouteMatch("~/Customer/List","Customer", "List"); TestRouteMatch("~/Customer/List/All","Customer", "List", new { id = "All" }); TestRouteMatch("~/Customer/List/All/Delete","Customer", "List", new { id = "All", catchall= "Delete" }); TestRouteMatch("~/Customer/List/All/Delete/Perm","Customer", "List", new { id = "All",catchall = "Delete/Perm" }); } 

Prioritizing Controllers by Namespaces
按命名空间区分控制器优先顺序

When an incoming URLmatches a route, the MVC Framework takes the value of the controller variableand looks for the appropriate name. For example, when the value of thecontroller variable is Home, then the MVC Framework looks for a controllercalled HomeController. This is an unqualified class name, which means that theMVC Framework doesn’t know what to do if there are two or more classes calledHomeController in different namespaces. When this happens, an error isreported, as shown in Figure 11-5.
当一个输入请求URL匹配一条路由时,MVC框架取得controller变量的值,并查找相应的(控制器)名字。例如,当controller变量的值是Home时,然后MVC框架查找名为HomeController。这是一个不合格的类名,意即,如果在不同的命名空间中有两个或更多个名为HomeController时,MVC框架将不知道怎么做。当这种情况发生时,会报出一个错误,如图11-5所示。

图11-5

Figure 11-5. An errorarising from ambiguous controller classes
图11-5. 由于多义控制器类引发的错误

This problem arises moreoften than you might expect, especially if you are working on a large MVCproject that uses libraries of controllers from other development teams orthird-party suppliers. It is natural to name a controller relating to useraccounts AccountController, for example, and it is only a matter of time beforeyou encounter a naming clash.
这个问题比你想象的更经常出现,特别是如果你在一个大型的MVC项目上工作,它采用了其它开发团队或第三方所提供的控制器库。例如,命名一个与用户账号相关的控制器AccountController,这是很自然的,而且你遇到命名冲突只是时间问题。


n Note To create the error shown in Figure 11-5, we added a classlibrary project called AdditionalControllers to our solution and added acontroller called HomeController. We then added a reference to our mainproject, started the application, and requested the URL /Home. The MVCFramework searched for a class called HomeController and found two: one in ouroriginal project and one in our AdditionalControllers project. If you read thetext of the error shown in Figure 11-5, you can see that the MVC Framework helpfullytells us which classes it has found.
注:为了显示图11-5的错误,我们把一个名为AdditionalControllers的类库项目添加到我们的解决方案,并添加一个名为HomeController的控制器。然后把引用添加到我们的主项目,启动应用程序,并请求/Home的URL。MVC框架搜索名为HomeController的一个类,发现有两个:一个在原项目中,一个在AdditionalControllers项目中。如果你阅读图11-5所示的错误文本,你可以看到MVC框架帮助性地告诉我们,它已经发现了哪些类。


To address this problem,we can tell the MVC Framework to give preference to certain namespaces whenattempting to resolve the name of a controller class, as demonstrated inListing 11-17.
为了解决这一问题,在试图解析控制器类的名字时,我们可以告诉MVC框架,对某些命名空间给予优先,如清单11-17所示。

Listing 11-17. SpecifyingNamespace Resolution Order

public static void RegisterRoutes(RouteCollectionroutes) { routes.MapRoute("MyRoute","{controller}/{action}/{id}/{*catchall}", new { controller = "Home", action ="Index", id = UrlParameter.Optional }, new[]{ "URLsAndRoutes.Controllers"}); } 

We express the namespacesas a string array. In the listing, we have told the MVC Framework to look inthe URLsAndRoutes.Controllers namespace before looking anywhere else. If asuitable controller cannot be found in that namespace, then the MVC Frameworkwill default to its regular behavior and look in all of the availablenamespaces.
我们把这些命名空间表示成数组。在上述列表中,我们告诉MVC框架,在查看其它空间之前,先查看URLsAndRoutes.Controllers命名空间。如果在这个命名空间中找不到合适的控制器,然后MVC框架将回到正常行为,查看所有可用的命名空间。

The namespaces added to aroute are given equal priority. The MVC Framework doesn’t check the firstnamespace before moving on to the second and so forth. For example, suppose weadded both of our project namespaces to the route, like this:
添加到一条路由的命名空间具有相同的优先级。MVC框架在在移动到第二个(命名空间)等等之前,不会检查第一个命名空间。例如,假设我们把两个项目的命名空间都加到这条路由,像这样:

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

We would see the sameerror as shown in Figure 11-5, because the MVC Framework is trying to resolvethe controller class name in all of the namespaces we added to the route. If wewant to give preference to a single controller in one namespace, but have allother controllers resolved in another namespace, we need to create multipleroutes, as shown in Listing 11-18.
我们会看到如图11-5所示的同样错误,因为,MVC框架试图解析我们添加到这条路由的所有命名空间中的控制器类。如果我们想给出一个命名空间中某一个控制器优先,但又要解析另一个命名空间中的其它控制器,我们就需要生成如清单11-18所示的多条路由。

Listing 11-18. UsingMultiple Routes to Control Namespace Resolution

public static void RegisterRoutes(RouteCollectionroutes) { routes.MapRoute("AddContollerRoute","Home/{action}/{id}/{*catchall}", new {controller = "Home", action = "Index", id =UrlParameter.Optional }, new[]{ "AdditionalControllers" }); routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}", new { controller = "Home", action= "Index", id = UrlParameter.Optional }, new[] {"URLsAndRoutes.Controllers"}); } 

n Tip In Chapter 14, we show you how to prioritize namespaces forthe entire application and not just a single route. This can be a neatersolution if you find yourself applying the same prioritization to all of yourroutes.
提示:在第14章中,我们将给你演示如何为整个应用程序来优先命名空间顺序,而不只是单个路由。如果你自己找到对所有路由运用同样的优先化(办法),这可能是一种更整洁的解决方案。


We can tell the MVCFramework to look only in the namespaces that we specify. If a matchingcontroller cannot be found, then the framework won’t search elsewhere. Listing11-19 shows how this feature is used.
我们可以告诉MVC框架只查看我们指定的命名空间。如果找不到一条匹配的控制器,那么框架不要搜索其它地方。清单11-19演示了如何使用这一特性。

Listing 11-19. DisablingFallback Namespaces

public static void RegisterRoutes(RouteCollectionroutes) { Route myRoute =routes.MapRoute("AddContollerRoute","Home/{action}/{id}/{*catchall}", new { controller = "Home",action = "Index", id = UrlParameter.Optional }, new[] {"AdditionalControllers" }); myRoute.DataTokens["UseNamespaceFallback"]= false; } 

The MapRoute methodreturns a Route object. We have been ignoring this in previous examples,because we didn’t need to make any adjustments to the routes that were created.To disable searching for controllers in other namespaces, we must take theRoute object and set the UseNamespaceFallback key in the DataTokens collectionproperty to false. This setting will be passed along to the componentresponsible for finding controllers, which is known as the controller factory,and which we discuss in detail in Chapter 14.
MapRoute方法返回一个Route对象。我们在前面的例子中忽略了它,因为我们不需要对我们生成的路由做任何调整。为了取消搜索其它命名空间的控制器,我们必须取得这个Route对象,并把DataTokens集合属性中的UseNamespaceFallback键值设置为false。这个设置将在负责查找控制器的沿途进行传递,这称为控制器工厂,我们会在第14章进行讨论。

Constraining Routes
约束路由

At the start of thechapter, we described how URL patterns are conservative in how they matchsegments and liberal in how they match the content of segments. The previousfew sections have explained different techniques for controlling the degree ofconservatism—making a route match more or fewer segments using default values,optional variables, and so on.
在本章开始,我们描述了URL模式在它们匹配片段方面是保守的,以及在它们匹配片段内容方面又是宽松的。前几小节已经解释了控制保守程度的不同技术— 用默认值使路由匹配或多或少的片段、可选变量等等。

It is now time to look athow we can control the liberalism in matching the content of URL segments—howto restrict the set of URLs that a route will match against. Once we havecontrol over both of these aspects of the behavior of a route, we can createURL schemas that are expressed with laser-like precision.
现在,到了考查宽松机制的时候了,看看我们如何控制匹配URL片段内容方面的宽松机制 — 即,如何限制一条路由将要匹配的一组URL。一旦我们有了路由行为这两方面(指保守和宽松两方面 — 译者注)的控制,我们就可以生成具有激光般精度的URL方案。

Constraining a Route Using a Regular Expression
用正则表达式约束路由

The first technique wewill look at is constraining a route using regular expressions. Listing 11-20contains an example.
我们要考查的第一项技术是用正则表达式约束路由。清单11-20含有一个例子。

Listing 11-20. Using aRegular Expression to Constrain a Route

public static void RegisterRoutes(RouteCollectionroutes) { routes.MapRoute("MyRoute","{controller}/{action}/{id}/{*catchall}", new { controller = "Home", action ="Index", id = UrlParameter.Optional }, new {controller = "^H.*"}, new[] {"URLsAndRoutes.Controllers"}); } 

We define constraints bypassing them as a parameter to the MapRoute method. Like default values,constraints are expressed as an anonymous type, where the properties of thetype correspond to the names of the segment variables we want to constrain.
我们通过把约束传递给MapRoute方法的办法定义约束。像默认值一样,约束被表示成一个匿名类型,在这里,类型属性对应于我们想要进行约束的片段变量名。

In this example, we haveused a constraint with a regular expression that matches URLs only where thevalue of the controller variable begins with the letter H.
在这个例子中,我们使用了一个带有正则表达式的约束,它只匹配controller变量的值以H字母打头的URL。


n Note Default values are used before constraints are checked. So,for example, if we request the URL /, the default value for controller, whichis Home, is applied. The constraints are then checked, and since the controllervalue beings with H, the default URL will match the route.
注:Default值是在约束被检查之前使用的。因此,如果我们请求的地址是/,controller的默认值会被运用,这里是Home。约束然后被检查,而由于controller的值以H打头,这个默认的URL与这条路由是匹配的。


Constraining a Route to a Set of Specific Values
将一条路由约束到一组特定的值

We can use regularexpressions to constrain a route so that only specific values for a URL segmentwill cause a match. We do this using the bar (|) character, as shown in Listing11-21.
我们可以用正则表达式来约束一条路由,以便对于一个URL片段,只有指定的值才会形成匹配。我们用竖线字符(|)来做这件事,如清单11-21所示。

Listing 11-21.Constraining a Route to a Specific Set of Segment Variable Values

public static void RegisterRoutes(RouteCollectionroutes) { routes.MapRoute("MyRoute","{controller}/{action}/{id}/{*catchall}", new {controller = "Home", action = "Index", id =UrlParameter.Optional }, new {controller = "^H.*", action ="^Index$|^About$"}, new[] {"URLsAndRoutes.Controllers"}); } 

This constraint will allowthe route to match only URLs where the value of the action segment is Index orAbout. Constraints are applied together, so the restrictions imposed on thevalue of the action variable are combined with those imposed on the controllervariable. This means that the route in Listing 11-21 will match URLs only whenthe controller variable begins with the letter H and the action variable isIndex or About. So, now you can see what we mean about creating very preciseroutes.
这个约束将允许这条路由只匹配cation片段的值是Index或About的URL。把约束合在一起,那么就是,影响action变量值的约束与影响controller变量的约束相结合。这意味着,清单11-21所示的路由将只匹配这样的URL:controller变量以H字母打头,而且action变量是Index或About。因此,现在你可以明白我们所说的“生成十分精确的路由”的含义。

Constraining a Route Using HTTP Methods
用HTTP方法约束路由

We can constrain routes sothat they match a URL only when it is requested using a specific HTTP method,as demonstrated in Listing 11-22.
我们可以约束路由,以使它们匹配只有用用指定的HTTP方法进行请求的URL,如清单11-22所示。

Listing 11-22.Constraining a Route Based on an HTTP Method

public static void RegisterRoutes(RouteCollectionroutes) { routes.MapRoute("MyRoute","{controller}/{action}/{id}/{*catchall}", new { controller = "Home", action ="Index", id = UrlParameter.Optional }, new { controller = "^H.*", action ="Index|About", httpMethod= new HttpMethodConstraint("GET") }, new[] { "URLsAndRoutes.Controllers"}); } 

The format for specifyingan HTTP method constraint is slightly odd. It doesn’t matter what name we givethe property, as long as we assign it an instance of the HttpMethodConstraintclass. In the listing, we called our constraint property httpMethod to helpdistinguish it from the value-based constraints we defined previously.
指定HTTP方法约束的格式有点奇怪。它与我们为此属性所给出的名字无关,只要我们给它赋一个HttpMethodConstraint类的实例。在上述清单中,我们把这个约束属性叫做httpMethod,以帮助我们区分它与前面定义的那些基于值的约束。


n Note The ability to constrain routes by HTTP method is unrelatedto the ability to restrict action methods using attributes such as HttpGet andHttpPost. The route constraints are processed much earlier in the requestpipeline, and they determine the name of the controller and action required toprocess a request. The action method attributes are used to determine whichspecific action method will be used to service a request by the controller. Weprovide details of how to handle different kinds of HTTP methods (including themore unusual ones such as PUT and DELETE) in Chapter 14.
注:借助HTTP方法约束路由的能力与使用诸如HttpGet和HttpPost属性限制动作方法的能力是无关的。在请求管道中,路由约束的处理要早得多,而且它们决定了需要对一个请求进行处理的controller和action的名字。动作方法属性用来确定由控制器用哪一个特定的动作方法对一个请求进行服务。我们在第14章将提供如何处理不同种类的HTTP方法(包括不太常用的PUT和DELETE)的细节


We pass the names of the HTTPmethods we want to support as string parameters to the constructor of theHttpMethodConstraint class. In the listing, we limited the route to GETrequests, but we could have easily added support for other methods, like this:
我们把希望支持的HTTP方法以字符串参数传递给HttpMethodConstraint类的构造器。在上述清单中,我们把这条路由限制到GET请求,但我们可以很容易地添加对其它方法的支持,像这样:

... httpMethod = newHttpMethodConstraint("GET", "POST") }, ... 

UNIT TESTING: ROUTE CONSTRAINTS
单元测试:路由约束


When testing constrainedroutes, it is important to test for both the URLs that will match and the URLsyou are trying to exclude, which you can do by using the helper methodsintroduced at the start of the chapter. As an example, here is the test methodthat we used to test the route defined in Listing 11-20:
当测试约束路由时,重要的是对所要匹配的URL和你试图排除的URL都要进行测试,你可以用本章开头介绍的辅助方法来做这件事。作为一个例子,以下是我们用来测试清单11-20所定义路由的测试方法。

[TestMethod] public void TestIncomingRoutes() { TestRouteMatch("~/","Home", "Index"); TestRouteMatch("~/Home","Home", "Index"); TestRouteMatch("~/Home/Index","Home", "Index"); TestRouteMatch("~/Home/About","Home", "About"); TestRouteMatch("~/Home/About/MyId","Home", "About", new { id = "MyId" }); TestRouteMatch("~/Home/About/MyId/More/Segments","Home", "About", new { id = "MyId", catchall = "More/Segments" }); TestRouteFail("~/Home/OtherAction"); TestRouteFail("~/Account/Index"); TestRouteFail("~/Account/About"); } 

Our helper methods alsoinclude support for testing HTTP method constraints. We just need to pass theHTTP method we want to test as a parameter to the TestRouteMatch andTestRouteFail methods, as shown in this test method, which tests the routedefined in Listing 11-21:
我们的辅助方法也包括了对测试HTTP方法约束的支持。我们只需要把我们想要测试的HTTP方法作为参数传递给TestRouteMatch和TestRouteFail方法,像上述测试方法一样,来测试清单11-21所定义的路由:

[TestMethod] public void RegisterRoutesTest() { TestRouteMatch("~/","Home", "Index", null, "GET"); TestRouteFail("~/","POST"); } 

If you don’tspecify an HTTP method, the helper methods default to the GET method.
如果你不指定一个HTTP方法,该辅助方法默认为GET方法。


Defining a Custom Constraint
定义一个自定义约束

If the standardconstraints are not sufficient for your needs, you can define your own customconstraints by implementing the IRouteConstraint interface. Listing 11-23demonstrates a custom constraint that operates on the user-agent informationprovided by a browser as part of a request.
如果标准约束不满足你的需求,你可以通过实现IRouteConstraint接口的办法,定义你自己的自定义约束。清单11-23演示了一个自定义约束,它针对用户代理信息进行操作,该代理信息是由浏览器作为请求的一部分所提供的。

Listing 11-23. Creating aCustom RouteConstraint

using System.Web; using System.Web.Routing; namespace URLsAndRoutes.Infrastructure { public class UserAgentConstraint :IRouteConstraint { private string requiredUserAgent; public UserAgentConstraint(string agentParam){ requiredUserAgent = agentParam; } public bool Match(HttpContextBasehttpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirectionrouteDirection) { return httpContext.Request.UserAgent != null&& httpContext.Request.UserAgent.Contains(requiredUserAgent); } } } 

The IRouteConstraintinterface defines the Match method, which an implementation can use to indicateto the routing system if its constraint has been satisfied. The parameters forthe Match method provide access to the request from the client, the route thatis being evaluated, the parameter name of the constraint, the segment variablesextracted from the URL, and details of whether the request is to check anincoming or outgoing URL. For our example, we check the value of the UserAgentproperty of the client request to see if it contains a value that was passed toour constructor. Listing 11-24 shows our custom constraint used in a route.
IRouteConstraint接口定义了Match方法,一个实现可以用该方法向路由系统指示,它的约束是否已经得到满足。Match方法的参数提供对以下对象的访问:客户端请求、进行评估的路由、约束的参数名、从URL提取的片段变量、以及请求是否要检查是输入还是输出URL的细节。例如,我们检查客户端请求的UserAgent属性的值,以看看它是否含有一个被传递给我们构造器的值。清单11-24显示了我们用在一条路由中的自定义约束。

Listing 11-24. Applying aCustom Constraint in a Route

public static void RegisterRoutes(RouteCollectionroutes) { routes.MapRoute("MyRoute","{controller}/{action}/{id}/{*catchall}", new { controller = "Home", action ="Index", id = UrlParameter.Optional }, new { controller = "^H.*", action ="Index|About", httpMethod = newHttpMethodConstraint("GET", "POST"), customConstraint= new UserAgentConstraint("IE") }, new[] { "URLsAndRoutes.Controllers"}); } 

In the listing, we haveconstrained the route so that it will match only requests made from browserswhose user-agent string contains IE, which includes requests from Microsoftbrowsers.
在此清单中,我们已经约束了这条路由,以使它只与用户代理含有IE字符串的浏览器发出的请求匹配,这包括了从微软浏览器发出的请求。


n Note To be clear, because this is the kind of thing we get lettersabout, we are not suggesting that you restrict your application so that itsupports only one kind of browser. We used user-agent strings solely todemonstrate custom route constraints and believe in equal opportunities for allbrowsers. We really hate web sites that try to force their preference forbrowsers on users.
注:我们要明白,这只一种我们得到字母的办法,我们不建议限制你的应用程序,以使它只支持一种浏览器。我们很少使用用户代理字符串来演示自定义路由约束,而奉信所有浏览器有同等的机会。我们很讨厌web网站把他们对浏览器的偏爱强加到用户头上。


Routing Requests for Disk Files
路由对磁盘文件的请求

Not all of the requestsfor an MVC application are for controllers and actions. We still need a way toserve content such as images, static HTML files, JavaScript libraries, and soon. As a demonstration, we have created a file called StaticContent.html in theContent folder of our example MVC application. Listing 11-25 shows this file’scontents.
并不是MVC应用程序的所有请求都是对controller和action的。我们还需要一种对内容进行服务的办法,如图像、静态HTML文件、JavaScript库等等。作为演示,我们在例子MVC应用程序的Content文件夹中生成了一个叫做StaticContent.html的文件。清单11-25显示了这个文件的内容。

Listing 11-25. TheStaticContent.html File

<html> <head><title>Static HTMLContent</title></head> <body>This is the static html file(~/Content/StaticContent.html)</body> </html> 

The routing systemprovides integrated support for serving such content. If you start theapplication and request the URL /Content/StaticContent.html, you will see thecontents of this simple HTML file displayed in the browser, as shown in Figure11-6.
路由系统提供了对这种内容进行服务的集成支持。如果你启动应用程序,并请求/Content/StaticContent.html,你会在看到这个例子HTML文件的内容显示在浏览器中,如图11-6所示。

图11-6

Figure 11-6.Requesting the static content file
图11-6. 请求静态内容文件

By default, the routingsystem checks to see if a URL matches a disk file before evaluating theapplication’s routes. If there is a match, then the disk file is served, andthe routes are never used. We can reverse this behavior so that our routes areevaluated before disk files are checked by setting the RouteExistingFilesproperty of the RouteCollection to true, as shown in Listing 11-26.
默认地,路由系统在评估应用程序的路由之前,会查看一个URL是否匹配一个磁盘文件。如果有一个匹配,那么这个磁盘文件就会被服务,于是就不会使用路由。我们可以通过把RouteConllection的RouteExistingFiles属性(该属性意为,对存在文件进行路由— 译者注)设置为true的办法,保留这种行为,以使你的路由在检查磁盘文件之前被评估。

Listing 11-26. EnablingRoute Evaluation Before File Checking

public static void RegisterRoutes(RouteCollectionroutes) { routes.RouteExistingFiles= true; routes.MapRoute("MyRoute","{controller}/{action}/{id}/{*catchall}", new { controller = "Home", action ="Index", id = UrlParameter.Optional }, new { controller = "^H.*", action ="Index|About", httpMethod = newHttpMethodConstraint("GET", "POST"), customConstraint = newUserAgentConstraint("IE") }, new[] { "URLsAndRoutes.Controllers"}); } 

The convention is to placethis statement close to the top of the RegisterRoutes method, although it willtake effect even if you set it after you have defined your routes. Once theproperty have been set to true, we can define routes that match URLs thatcorrespond to disk files, such as the one shown in Listing 11-27.
如果把这条语句放在你定义路由之后,虽然这样也会起作用,但惯例是把这条语句放在紧靠RegisterRoutes方法的顶部。一旦此属性被设置为true,我们就可以定义路由来匹配响应磁盘文件的URL,例如,清单11-27所示的路由。

Listing 11-27. A Route Whose URLPattern Corresponds to a Disk File

public static void RegisterRoutes(RouteCollectionroutes) { routes.RouteExistingFiles = true; routes.MapRoute("DiskFile","Content/StaticContent.html", new { controller= "Account", action = "LogOn", }, new { customConstraint= new UserAgentConstraint("IE") }); routes.MapRoute("MyRoute","{controller}/{action}/{id}/{*catchall}", new { controller = "Home", action ="Index", id = UrlParameter.Optional }, new { controller = "^H.*", action ="Index|About", httpMethod = new HttpMethodConstraint("GET","POST"), customConstraint = newUserAgentConstraint("IE") }, new[] { "URLsAndRoutes.Controllers"}); } 

This route maps requestsfor the URL Content/StaticContent.html to the LogOn action of the Account controller.We have added a constraint to the route, which means that it will match onlyrequests that are made from browsers whose user-agent string contains Chrome.(This is a contrived example to demonstrate combining these features; we arenot suggesting you do things like this in a real application!)
这条路由把Content/StaticeContent.html的URL请求映射到Account控制器的LogOn动作方法上。我们添加了一条对路由的约束,这意味着它只匹配用户代理字符串含有Chrome(好像应该是IE — 译者注)的浏览器所发出的请求。(这只是一个人为的例子,以演示这些特性的组合;我们不建议你在一个真实的应用程序中做这种事!)

When theRouteExistingFiles property is enabled, disk files will be delivered to clientsonly if there is no matching route for the request. For our example route, thismeans that Internet Explorer users will get the response from the Accountcontroller, while all other users will see the static content. You can see theURL mapping at work in Figure 11-7.
当RouteExistingFiles属性被启用时,只在没有与请求匹配的路由时,磁盘文件才会被投递给客户端。对于我们的例子路由来说,这意味着,Internet Explorer浏览器用户将得到Account控制器的响应,而所有其它用户将看到静态内容。你可以在图11-7中看到映射的URL在起作用。

图11-7

Figure 11-7.Intercepting a request for a disk file using a route
图11-7. 用一条路由窃听一个磁盘文件的请求

Routing requests intendedfor disk files requires careful thought, not least because URL patterns willmatch these kinds of URL as eagerly as any other. For example, a request for/Content/StaticContent.html will be matched by a URL pattern such as{controller}/{action}. Unless you are very careful, you can end up with someexceptionally strange results and reduced performance. So, enabling this optionis very much a last resort. To demonstrate this, we have created a second HTMLfile in the Content directory, called OtherStaticContent.html, and added a newroute to the RegisterRoutes method, as shown in Listing 11-28.
对磁盘文件请求进行路由需要小心考虑,不仅仅是因为URL模式将像其它一样热心地匹配这些种类的URL。例如,一个对/Content/StaticContent.html的请求将被{controller}/{action}这样的URL模式匹配。除非你特别小心,否则你可能终止于一些异常奇怪的结果,并且降低了性能。因此,启动这一选项是非常不得已的。为了演示这种情况,我们在Content目录中生成了第二个文件,名为OtherStaticContent.html,并添加一条新路由到RegisterRoutes方法,如清单11-28所示。

Listing 11-28. Adding aRoute to the RegisterRoutes Method

public static void RegisterRoutes(RouteCollectionroutes) { routes.RouteExistingFiles = true; routes.MapRoute("DiskFile","Content/StaticContent.html", new { controller = "Account", action ="LogOn", }, new { customConstraint = newUserAgentConstraint("IE") }); routes.MapRoute("MyNewRoute","{controller}/{action}"); routes.MapRoute("MyRoute","{controller}/{action}/{id}/{*catchall}", new { controller = "Home", action ="Index", id = UrlParameter.Optional }, new { controller = "^H.*", action ="Index|About", httpMethod = newHttpMethodConstraint("GET", "POST"), customConstraint = newUserAgentConstraint("IE") }, new[] { "URLsAndRoutes.Controllers"}); } 

When we request the URLContent/OtherStaticContent.html, our request matches against the URL we addedin Listing 11-28, so that the target controller is Content and the actionmethod is OtherStaticContent.html. This will happen with any request for a filewhose URL has two segments. Of course, there is no such controller or actionmethod, and the user will be sent a 404 – Not Found error.
当我们请求Content/OtherStaticContent.html这个URL时,我们的请求与我们在清单11-28中添加的URL匹配,于是,目标控制器是Content,动作方法是OtherStaticContent.html。文件的URL具有两个片段的任何请求都会发生这种情况。当然,这种控制器或动作方法是没有的,于是用户会收到一个404 — 未找到的错误。

Bypassing the Routing System
绕过路由系统

Setting theRouteExistingFiles property, which we demonstrated in the previous section,makes the routing system more inclusive. Requests that would normally bypassthe routing system are now evaluated against the routes we have defined.
我们在上一小节演示的,设置RouteExistingFiles属性使路由系统更宽松。通常会绕过路由系统的请求,现在会根据我们所定义的路由进行评估。

The counterpart to thisfeature is the ability to make the routing system less inclusive and prevent URLsfrom being evaluated against our routes. We do this by using the IgnoreRoutemethod of the RouteCollection class, as shown in Listing 11-29.
与这一特性对应的是,使路由系统少一些宽松,并阻止URL被我们的路由评估的能力。我们通过使用RouteCollection类的IgnoreRoute方法来做这件事,如清单11-29所示。

Listing 11-29. Using theIgnoreRoute Method

public static void RegisterRoutes(RouteCollectionroutes) { routes.RouteExistingFiles = true; routes.MapRoute("DiskFile","Content/StaticContent.html", new { controller = "Account", action ="LogOn", }, new { customConstraint = newUserAgentConstraint("IE") }); routes.IgnoreRoute("Content/{filename}.html"); routes.MapRoute("","{controller}/{action}"); routes.MapRoute("MyRoute","{controller}/{action}/{id}/{*catchall}", new { controller = "Home", action ="Index", id = UrlParameter.Optional }, new { controller = "^H.*", action ="Index|About", httpMethod = newHttpMethodConstraint("GET", "POST"), customConstraint = newUserAgentConstraint("IE") }, new[] { "URLsAndRoutes.Controllers"}); } 

We can use segmentvariables like {filename} to match a range of URLs. In this case, the URLpattern will match any two-segment URL where the first segment is Content andthe second content has the .html extension.
我们可以用{filename}这样的片段变量来匹配URL的范围。在这种情况下,URL模式将匹配任何两片段URL,第一个片段是Content,第二片段的内容有.html扩展名。

The IgnoreRoute methodcreates an entry in the RouteCollection where the route handler is an instanceof the StopRoutingHandler class, rather than MvcRouteHandler. The routingsystem is hard-coded to recognize this handler. If the URL pattern passed tothe IgnoreRoute method matches, then no subsequent routes will be evaluated,just as when a regular route is matched. It follows, therefore, that where weplace the call to the IgnoreRoute method is significant. In Listing 11-27, weused this feature to minimize the impact of the RouteExistingFiles property bynot routing any requests for HTML files.
IgnoreRoute方法在RouteCollection中生成一个条目,在这里,路由处理句柄是StopRoutingHandler类的一个实例,而不是MvcRouteHandler。路由系统被硬编码来识别这个句柄。如果传递给IgnoreRoute方法的URL模式匹配,那么,后继的路由将不被评估,就像有一条常规路由被匹配时那样。因此,它遵循,对IgnoreRoute方法的调用放在哪儿是重要的这一规则。在清单11-27中,我们通过不对HTML文件的任何请求进行路由的办法,使用了这一特性,以最小化RouteExistingFiles属性的冲突。

posted on 2012-06-13 20:54  lucky.net  阅读(296)  评论(0编辑  收藏  举报

导航

Copyright luckynet 2013