ASP.NET MVC URL、路由、与区域 Part1

以下内容摘自:http://www.cnblogs.com/r01cn/archive/2011/11/16/2251693.html

感谢作者的翻译,这里只是译文。原书名:Pro ASP.NET MVC 3 Framework

 

第十一章 URL、路由、与区域 PART1

 

 

在引入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错误

这种方式对Web表单工作得很好,在这里,每一个ASPX页面既是一个文件,又是一个对请求自包含的响应。这对MVC应用程序没什么意义,在这里,请求是由控制器类中的动作方法处理的,而且与硬盘上的文件没有一对一的相互关系。

为了处理MVC的网址,ASP.NET平台使用了路由系统。在本章中,我们将向你演示,如何建立和使用路由系统,以便为你的项目生成功能强大而灵活的URL处理。正如你将看到的,路由系统可以让你生成你所希望的任何URL模式,并且以清晰而简洁的方式表达他们。

路由系统介绍

路由系统有两个功能:

·         检查一个输入请求(1100.210应当是误输译者注),并想象出该请求想要的是哪个控制器和动作。正如你所期望的,在我们接收到一个客户端请求时,这就是我们希望路由系统去做的事情。

·         生成输出URL。这些是我们的视图渲染的HTML中出现的URL,以便用户点击这些连接时调用一个特定的动作(这时,它已经再次变成了输入URL)。

在本章的第一部分,我们将关注于定义路由,并使用它们去处理输入URL,以使用户能够到达你的控制器和动作。然后,我们将向你演示,如何使用那些同样的路由,来生成你需要包含在你的HTML中的输出URL

路由系统程序集

虽然路由系统是ASP.NETMVC框架所需要的,但也期望它被其它ASP.NET技术所使用,包括Web表单。由于这一原因,路由系统类在System.Web程序集中,而不是在System.Web.Mvc中。

当你生成一个新MVC应用程序时,你将看到Visual Studio已经添加了一个对System.Web.Routing程序集的引用。这是一个延期支持.NET 3.5的程序集(意即在.NET 4.0中仍用它对.NET 3.5进行支持,但对.NET 4.0的应用程序可以不用它译者注),而且对你的项目没有影响。如果你愿意,你可以删除这个引用。

我们关注于与MVC框架一起使用路由,但大量的信息,在与ASP.NET平台的其它部分使用路由时,也是适用的。Adam在他的《Applied ASP.NET 4 in Context》一书中包含了大量关于ASP.NET基础平台和Web表单使用路由的信息。

生成路由项目

为了演示路由系统,我们需要一个能够对之添加路由的项目。我们用Internet应用程序模板生成了一个新MVC应用程序,此项目名为UrlsAndRoutes。我们选择这个模板是因为它有一些现成的控制器和动作。

路由是在Global.asax中定义的。如果你在Visual Studio中打开这个文件,你将看到路由占据了这个文件的很大一部分(诚然,这还是短的)。清单11-1显示了我们项目的Global.asax,我们对它作了点编辑,以使它更可读。

注:严格地说,路由是在Global.asax.cs中定义的,这是Global.asax的后台代码文件(有些文章或书籍中也叫隐藏代码文件译者注)。当你在解决方案窗口中双击Global.asax时,Visual Studio实际上打开Global.asax.cs。出于这一原因,我们把这两个文件都称为Global.asax

Listing 11-1. The Default Global.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 void RegisterRoutes(RouteCollection routes) {
            ... routes are defined here...
        }

        publicstatic  void RegisterGlobalFilters(GlobalFilterCollection filters) {
            filters.Add( new HandleErrorAttribute());
        }
    }
}

Application_Start方法是由当前ASP.NET平台在应用程序启动时首先调用的,它导致RegisterRoutes方法被调用。送给这个方法的参数是静态的RouteTable.Routes属性的值,这是RouteCollection类的一个实例。

我们已经从RegisterRoutes方法中删除了被默认添加的路由,因为我们想给你演示生成路由的各种技术,以及可用路由的不同种类。在我们能够做这些事情之前,我们需要退一步,先考察一些路由系统核心的东西:URL模式。

介绍URL模式

路由系统用一组路由来实现它的魔法。这些路由共同组成了应用程序的URL模式或方案,这个URL模式(或方案)是你的应用程序能够认识并能对之作出响应的一组URL

我们不需要手工键入我们打算支持的所有个别的URL。而是,每条路由都含有一个URL模式,用它与一个输入URL进行比较。如果该模式与这个URL匹配,那么它(URL模式)便被路由系统用来对这个URL进行处理。

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

让我们从SportsStore应用程序的URL例子开始:

http://mysite.com/Admin/Index

这是我们用来访问产品目录的管理员视图的URL。如果你回过头去参考第9章,你会看到这个URL指向了AdminController类中的Index动作方法。

URL可以被分解成片段。这些片段是这个URL除主机名和查询字串以外的、以/字符分隔的各个部分。在这个URL示例中,有两个片段,如图11-1所示。

图11-1

图11-1. 示例URL的片段 

第一片段含有单词Admin,而第二片段含有单词Index。很显然,第一个片段与控制器有关,第二片段与动作有关。但是,当然地,我们需要以一种路由系统可以理解的方式来表示这种关系。以下是做这件事的一个URL模式:

{controller}/{action}

当处理一个输入URL时,路由系统的工作是把这个URL与一个模式匹配,然后针对这个模式中定义的片段变量,从这个URL中提取相应的值。片段变量用花括号({}字符)表示。这个示例模式有名字为controlleraction的两个片段变量。

我们说,与一个模式匹配,因为一个MVC应用程序通常会有几个路由,而路由系统会把输入URL与每个路由的URL模式相比较,直到找到一条匹配的。

注:路由系统没有任何控制器和动作的专门知识。它只是为片段变量提取值,并沿请求管道传递它们(注意,管道中传递的是 —译者注)。在管道之后,当请求恰好到达MVC框架之时,其含意(指)被赋给controlleraction变量。这是为什么路由系统可以被用于Web表单、以及我们能够如何生成我们自己的变量的原因。

默认地,一个URL模式将匹配有正确片段数的任何URL。例如,上例的模式将匹配任何有两个片段的URL,如表11-1所示。

11-1. 匹配URL

请求URL

片段变量

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
不匹配片段太多

11-1高亮了URL模式的两个关键行为:

·         URL模式是保守的,而且只匹配与模式具有相同片段数的URL。你可以在表中的第四、第五个例子看到这种情况。

·         URL模式是宽松的。如果一个URL正好具有正确的片段数,该模式就会提取片段变量的值,而不管它可能是什么。

这些是默认行为,这是理解URL模式如何运行的关键。你将在本章稍后看到如何修改这种默认行为。

正如我们提到过的,路由系统并不知道关于MVC应用程序的任何情况,因此,即使在没有控制器或动作与从一个URL提取出来的值相符时,路由系统也会进行匹配。你可以通过表11-1的第二个例子明白这种情况的演示。我们在URL中传递了AdminIndex片段,因此,从这个URL提取的值也已经被传递(路由系统并不管是否有相应的控制器和动作方法译者注)。

生成并注册一个简单的路由

一旦你在头脑中已经有了一个URL模式,你就可以用它来定义一个路由。清单11-2演示了,如何用上一小节的URL模式示例,在Global.asaxRegisterRoutes方法中生成一条路由。

Listing 11-2. Registeringa Route

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

}

我们生成了一个新的路由对象,以我们的URL模式作为构造器参数。我们也传递了MvcRouteHandler的一个实例。不同的ASP.NET技术提供了不同的类来设计路由的行为,而这个(指MvcRouteHandler译者注)是我们用于ASP.NET MVC应用程序的类。一旦我们已经生成了这个路由,我们就可以用Add方法把它添加到RouteCollection对象,在其中传递我们给这个路由所起的名字,和我们已经生成的这个路由。

提示:命名你的路由是可选的,而且有一种争论,这样做牺牲了路由其它方面的一些关注分离(这句可能没译好译者注)。我们对命名是相当宽松的,但我们会在本章稍后的从指定路由生成一个URL”小节中,解释为什么这可能是一个问题。

注册路由的一个更方便的办法,是使用在RouteCollection类中定义的MapRoute方法。清单11-3演示了我们如何才能用这个方法来注册我们的路由。

Listing 11-3. Registering a Route Using the MapRoute Method

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

这个办法要更紧凑一点,主要是因为我们不需要生成MvcRouteHandler的一个实例。MapRoute方法专用于MVC应用程序。ASP.NETWeb表单应用程序可以用MapPageRoute方法,它也是在RouteConllection类中定义的。

单元测试:测试输入路由

即使你对应用程序的其余部门不采用单元测试,我们也建议你单元测试路由,以确保它们按预期的来处理输入URL。在大型应用程序中,URL方案可能会相当复杂,很容易生成一些意外的结果。

在上一章中,为了保持单元测试描述是自包含的,我们已经避免生成在测试之间共享的通用辅助方法。对于本章,我们要采用不同的办法。测试一个应用程序的路由方案是最容易完成的,因为你可以在一个单一的方法中批处理几个测试。并且,利用某些辅助方法,这会变得容易很多。

为了测试路由,我们需要模仿三个类:HttpRequestBaseHttpContextBase、和HttpResponseBase(最后一个是测试输出URL所需要的,我们会在本章稍后涉及它)。合在一起,这些类重新生成了足以支持路由系统的MVC架构。

以下是生成这些模仿对象的辅助方法,我们把它加到了我们的测试项目:

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

这里的setup相对简单。我们通过HttpRequestBase类的AppRelativeCurrentExecutionFilePath属性暴露了我们想要测试的URL,并且通过模仿HttpContextBase类的Request属性暴露了HttpRequestBase。我们的下一个方法让我们测试一个路由:

private  void TestRouteMatch( string url,  string controller,  string action,  object
routeProperties =  nullstring 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));
}

该方法的参数让我们指定要测试的URLcontrolleraction片段变量所期望的值,以及一个对象,它含有为我们已经定义的附加变量所期望的值。我们在本章稍后将给你演示如何生成这种变量。我们也为HTTP方法定义了一个参数,我们将在强制路由小节解释它。

TestRouteMatch方法依赖于另一个方法,TestIncomingRouteResult,以比较从路由系统获得的结果和我们期望的分段变量的值。这个方法使用了.NET反射,以使我们能够使用一个匿名类型来表示任何附加的分段变量。如果对这个方法不理解,不用着急,因为其作用只是让测试更方便:它不是理解MVC所需要的。

以下是这个TestIncomingRouteResult方法:

private  bool TestIncomingRouteResult(RouteData routeResult,  string controller,
string action,  object propertySet =  null) {
    Func< objectobjectbool> 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;
}

我们也需要一个方法来检查一个URL不工作。正如你将看到的,这可能是定义一个URL方案的重要部分。

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

TestRouteMatchTestRouteFail包含了对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 ");
}

这个测试用TestRouteMatch方法来检查我们期望的URL。而且也检查一个同样格式的URL,以确保用URL分段恰当地获得controlleraction的值。我们也用TestRouteFail方法来确保我们的应用程序不接收有不同分段数目的URL。当进行测试时,我们必须用波浪线(~)字符作为URL的前缀,因为这是ASP.NET框架如何把URL表现给路由系统的。

注意,我们不需要在测试方法中定义路由。这是因为我们从Global.asax类中的RegisterRoutes方法中直接装载它们。

你可以通过启动应用程序看到我们已经定义的路由的结果。当浏览器请求默认URLhttp://localhost:<端口>),应用程序将返回一个“404 — 未找到响应。这是因为我们还没有生成用于这个URL的路由,只支持{controller}/{action}格式。为了测试这种URL,导航到~/Home/Index。你可以看到应用程序生成了如图11-2所示的结果。

图11-2

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

 

我们的URL模式已经处理了这个URL,并为controller变量提取了Home值,为action变量提取了Index值。MVC框架把这个请求映射到了Home控制器的Index方法,这是在我们选择Internet应用程序的MVC项目模板时,VS为我们已经生成的。

至此,你已经生成了你的第一个路由,并用它来处理一个输入URL。在以下小节中,我们给你演示如何生成更复杂的路由,给出用于MVC应用程序的更丰富和更灵活的URL方案。

定义默认值

当我们请求应用程序的默认URL时,出现错误的原因是它不匹配我们已经定义的路由。默认URL被表示成~/送给路由系统,因此,没有与controlleraction变量匹配的片段。

前面我们曾解释过,URL模式是保守的,它们只匹配指定片段数的URL。我们也说过,这是默认行为。修改这种行为的一个办法是使用默认值。当URL不含与一个值匹配的片段时,用默认值。清单11-4提供了一个含有默认值的路由的例子。

Listing 11-4. Providing aDefault Value in a Route

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

默认值是作为匿名类型的属性来提供的。在清单11-4中,我们已经为action变量提供了一个Index的默认值。这个路由将匹配所有两片段的URL,就像它之前所做的那样。例如,如果请求http://mydomain.com/Home/Index,该路由将为controller提取Home值,为action提取Index值。

现在,我们已经为action片段提供了一个默认值,该路由也将匹配单片段URL匹配。当处理URL时,路由系统将从这个单片段URL提取controller值,并为action变量这个默认值。这样,我们可以请求http://mydomain.com/Home,并调用Home控制器上的Index动作方法。

我们可以继续向前,并定义根本不含任何片段变量的URL,只依靠默认值来标识controlleraction。我们可以用默认值为这两者映射默认URL,如清单11-5所示。

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

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

通过为controlleraction变量提供默认值,我们已经生成了与零个、一个、或两个片段的ULR匹配的路由,如表11-2所示。

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

我们在输入URL中接收的片段越少,我们依靠的默认值越多。如果我们再次运行此应用程序,浏览器将再次请求默认URL,但这次我们的新路由将起作用了,并为controlleraction添加默认值,允许这个输入URL被映射到Home控制器中的Index动作,如图11-3所示。

图11-3

图11-3. 添加一条用于默认URL的路由 

单元测试:默认值

如果我们用我们的辅助方法来测试定义默认值的路由,我们不需要采取任何特殊的动作。例如,以下是对清单11-5路由的简单测试:

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

需要说明的一点是,我们必须把默认URL指定为~/,因为这是ASP.NET如何把URL表示给路由系统的。如果我们用空字符串(””)或/来定义路由,路由系统将弹出一个异常,而测试将会失败。

使用静态URL片段

并不是一个URL模式中的所有片段都需要是可变的。你也可以生成具有静态片段的模式。假如我们想匹配像这样的URL,以支持带有前缀PublicURL

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

我们可以通过使用像清单11-6所示的模式来做这件事。

Listing 11-6. A URL Pattern with Static Segments

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

}

这个URL模式将只匹配含有三个片段的URL,第一个必须是Public(原文为Customers,不对译者注)。其它两个片段可以含有任何值,并将被用于controlleraction变量。

我们也可以生成既有静态也有可变元素的片段的URL模式,比如,清单11-7所表示的这种。

Listing 11-7. A URL Pattern with a Mixed Segment

public  static  void RegisterRoutes(RouteCollection routes) {
     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 " });
}

这条路由中的模式匹配任意两片段URL,而第一个片段以字母X打头。用于controller的值取自第一个片段,除X以外。action的值取自第二个片段。作为一个例子,以下URL与这条路由匹配:

http://mydomain.com/XHome/Index

这个URL被指向Home控制器上的Index动作方法。

路由排序

在清单11-6中,我们定义了一条新路由,并把它放在RegisterRoutes方法中的所有其它路由之前。我们这么做,是因为路由是以它们在RouteCollection对象中出现的顺序被运用的。MapRoute方法把一条路由添加到该集合的末端,意即,路由一般地是按我们添加他们的顺序被运用的。我们说一般地,是因为有办法让我们把路由插入到指定位置。我们不倾向于使用这种办法,因为让路由以它们被定义的顺序来运用,更容易理解用于一个应用程序的路由。

路由系统试图根据首先被定义的路由模式来匹配一个输入URL,并且只在不匹配时才会处理下一条路由。路由依次被试,直到找到匹配的一条,或这组路由被试完。其结果是,我们必须首先定义比较特殊的路由。我们在清单11-7中添加的路由要比以下路由更特殊。假设我们颠倒路由的顺序,像这样:

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

那么,第一条路由,它匹配任何具有零、一、二片段的URL,将是被使用的一条。更特殊的路由,现在是该清单的第二条,将是不可到达的。这条新路由去除一条URL的前导X,但旧路由(指第一条 译者注)却做不到,因此,像这样的一条URL

http://mydomain.com/XHome/Index

将以名为XHome的控制器为目标,而这是不存在的,因此,将导致一个“404 — 未找到错误被发送给用户。

如果你还没有阅读单元测试输入URL这一小节,我们建议你现在就读。如果你只对你应用程序的一个部分做单元测试,这应该就是你的URL方案。

我们可以结合静态片段和默认值为特定的路由生成一个别名。如果你已经公开地发布了你的URL方案,并且它与你的用户形成了标准合同,那末,这可能是用有的。如果你在这种情况下重构你的应用程序,你需要保留以前的URL格式。让我们设想,我们以前用一个Shop控制器,现在要由Home控制器来替代。清单11-8演示了我们如何才能生成一个保留旧式URL方案的路由。

Listing 11- 8. Mixing Static URL Segments and Default Values

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

我们添加的路由匹配任何两片段的URL,第一个片段是Shopaction的值取自第二个URL片段。这个URL模式不含对控制器的可变片段,因而使用我们所提供的默认值。这意味着对Shop控制器上一个动作的请求被转换成对Home控制器的请求。而且,我们可以更进一步,生成动作方法的别名,它也被重构,且不再出现在控制器中。为此,我们简单地生成一个静态URL,并给controlleraction提供默认值,如清单11-9所示。

Listing 11-9. Aliasing a Controller and an Action

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

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

再一次注意,我们放置新路由的位置,以使它先被定义。这是因为它比后面的路由更特殊。例如,如果一个对Shop/OlaAction的请求被下一条路由定义来处理,我们就会得到与我们想要的不同的结果。这个请求将被处理成一个“404 — 未找到错误,而不是被转换以保持与我们客户的标准合同。

单元测试:测试静态片段

再一次地,我们可以把我们的辅助方法用于其URL模式含有静态片段的路由。

以下是测试清单11-8中所添加的路由的例子:

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

定义自定义片段变量

我们并不受限于controlleraction变量。我们也可以定义自己的变量,如清单11-10所示。

Listing 11-10. Defining Additional Variables in a URL Pattern

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

该路由的URL模式定义了典型的controlleraction变量,以及一个名为id的自定义变量。这条路由将匹配任何0-3个片段的URL。第三个片段的内容将被赋给id变量,而如果没有第三个片段,将采用默认值。

注意:有些名字是保留的,因而对自定义片段变量名是非法的。这些是controlleraction、和area。前两个的含义是显然的,我们将在本章稍后的与区域一起工作小节中解释区域(area)的作用。

我们可以通过使用RouteData.Values属性,在一个动作方法中访问任何一个片段变量。为了对此演示,我们把一个叫做CustomVariable的动作方法添加到HomeController类,如清单11-11所示。

Listing 11-11. Accessing a Custom Segment Variable in an Action Method

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

这个方法获取路由的URL模式中的自定义变量的值,并用ViewBag把它传递给视图。清单11-12显示了该方法的相应视图,CustomVariable.cshtml,我们把它放在了/Views/Home文件夹中。

Listing 11-12. Displaying the Value of a Custom Segment Variable

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

如果你运行应用程序,并导航到/Home/CustomVariable/Hello网址,Home控制器中的CustomVaiable动作方法就会被调用,自定义片段变量的值就会通过ViewBag被接收并被显示出来,如图产11-4所示。

图11-4

图11-4. 显示自定义片段变量的值

 

单元测试:测试自定义片段变量

在我们的测试辅助方法中,我们包括了对测试自定义片段变量的支持。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 ");
}

 

用自定义变量作为动作方法参数

使用RouteData.Values属性只是访问自定义路由变量的一种办法。另一种办法要优雅得多。如果我们用匹配URL模式的变量名定义动作方法的参数,MVC框架将把从URL获得的这个值作为参数传递给该动作方法。例如,我们在清单11-8中定义的名为id的自定义变量。我们可以修改CustomVariable动作方法,以使它具有一个匹配的参数,如清单11-13所示。

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

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

当路由系统根据我们在清单11-8中所定义的URL来匹配一个URL时,URL中第三片段的值被传递给自定义变量indexMVC框架比较片段变量列表和动作方法参数,如果名字匹配,便把URL的值传递给该动作方法。

我们已经把id参数定义为一个字符串,但MVC框架会试图把RUL的值转换成我们所定义的任何参数类型。如果我们把id参数声明为一个int或一个DateTime,那么我们从URL模式接收到的值将被解析成该类型的一个实例。这是一个雅致而有用的特性,它去除了我们自己处理转换的需要。

注:MVC框架采用模型绑定系统来把包含在URL中的值转换成.NET类型,并且能够处理比这个例子复杂得多的情况。我们将在第17章涉及模型绑定。

定义可选URL片段

可选URL片段是指用户不需要指定,但又未指定默认值的片段。清单11-14显示了一个例子。我们通过把默认值设置为UrlParameter.Optional的办法把一个片段变量指定为可选的,如清单中的黑体所示。

Listing 11-14. Specifying an Optional URL Segment

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

这条路由将匹配不管是否提供idURL。表11-3显示了对不同的URL,它是如何工作的。

Table 11-3. Matching URLs with 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

正如你从上表所看到的,id变量只有当输入URL中存在相应片段时,才会被添加到变量集中。需要明确的是,当没有提供相应的片段时,id的值不是null,而是此时id变量未定义。

如果你需要知道用户是否提供了一个值,这一特性是有用的。如果我们对id参数提供一个默认值,并在动作方法中接收这个值,我们就说不出是否有这个默认值,还是用户只是偶然地请求了一个含有这个默认值的URL

可选片段的一个通常的运用是强迫关注分离,以使动作方法参数的默认值不包含在路由定义中。如果你想实践一下,你可以用C#可选参数特性来定义你的动作方法参数,如清单1-15所示。

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

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

单元测试:可选URL片段

当测试可选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 ");
}

定义可变长路由

改变URL模式默认保守性的另一个办法是,接收一个可变数目的URL片段。这允许你在一个单一的路由中路由任意长度的URL。通过设计一个叫做catchall(全匹配)的片段变量,以星号(*)为其前缀,你可以定义对可变片段的支持,如清单11-16所示。(所谓可变长是指片段数可变译者注)

Listing 11-16.Designating a Catchall Variable

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

我们已经通过上例添加一个catchall片段变量扩展了路由,我们形象地称之为全匹配。现在这个路由将匹配任何URL。前三个片段用于分别设置controlleraction、和id变量的值。如果URL含有更多片段,它们都被赋给catchall变量,如表11-4所示。

Table 11-4. Matching URLs with 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

这个路由中的URL模式匹配的片段数目没有上限。注意,由catchall捕获的片段是以片段/片段/片段的形式表示的。我们有责任对这个字符串进行处理,把它分解成一个个片段。

单元测试:测试catchall片段变量

我们可以像对待一个自定义变量一样来处理一个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 " });
}

按命名空间区分控制器优先顺序

当一个输入请求URL匹配一条路由时,MVC框架取得controller变量的值,并查找相应的(控制器)名字。例如,当controller变量的值是Home时,然后MVC框架查找名为HomeController。这是一个不合格的类名,意即,如果在不同的命名空间中有两个或更多个名为HomeController时,MVC框架将不知道怎么做。当这种情况发生时,会报出一个错误,如图11-5所示。

图11-5

图11-5. 由于多义控制器类引发的错误

 

这个问题比你想象的更经常出现,特别是如果你在一个大型的MVC项目上工作,它采用了其它开发团队或第三方所提供的控制器库。例如,命名一个与用户账号相关的控制器AccountController,这是很自然的,而且你遇到命名冲突只是时间问题。

注:为了显示图11-5的错误,我们把一个名为AdditionalControllers的类库项目添加到我们的解决方案,并添加一个名为HomeController的控制器。然后把引用添加到我们的主项目,启动应用程序,并请求/HomeURLMVC框架搜索名为HomeController的一个类,发现有两个:一个在原项目中,一个在AdditionalControllers项目中。如果你阅读图11-5所示的错误文本,你可以看到MVC框架帮助性地告诉我们,它已经发现了哪些类。

为了解决这一问题,在试图解析控制器类的名字时,我们可以告诉MVC框架,对某些命名空间给予优先,如清单11-17所示。

Listing 11-17. SpecifyingNamespace Resolution Order

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

我们把这些命名空间表示成数组。在上述列表中,我们告诉MVC框架,在查看其它空间之前,先查看URLsAndRoutes.Controllers命名空间。如果在这个命名空间中找不到合适的控制器,然后MVC框架将回到正常行为,查看所有可用的命名空间。

添加到一条路由的命名空间具有相同的优先级。MVC框架在在移动到第二个(命名空间)等等之前,不会检查第一个命名空间。例如,假设我们把两个项目的命名空间都加到这条路由,像这样:

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

 

我们会看到如图11-5所示的同样错误,因为,MVC框架试图解析我们添加到这条路由的所有命名空间中的控制器类。如果我们想给出一个命名空间中某一个控制器优先,但又要解析另一个命名空间中的其它控制器,我们就需要生成如清单11-18所示的多条路由。

Listing 11-18. Using Multiple Routes to Control Namespace Resolution

public  static  void RegisterRoutes(RouteCollection routes) {
     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 "});
}

提示:在第14章中,我们将给你演示如何为整个应用程序来优先命名空间顺序,而不只是单个路由。如果你自己找到对所有路由运用同样的优先化(办法),这可能是一种更整洁的解决方案。

我们可以告诉MVC框架只查看我们指定的命名空间。如果找不到一条匹配的控制器,那么框架不要搜索其它地方。清单11-19演示了如何使用这一特性。

Listing 11-19. Disabling Fallback Namespaces

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

MapRoute方法返回一个Route对象。我们在前面的例子中忽略了它,因为我们不需要对我们生成的路由做任何调整。为了取消搜索其它命名空间的控制器,我们必须取得这个Route对象,并把DataTokens集合属性中的UseNamespaceFallback键值设置为false。这个设置将在负责查找控制器的沿途进行传递,这称为控制器工厂,我们会在第14章进行讨论。

约束路由

在本章开始,我们描述了URL模式在它们匹配片段方面是保守的,以及在它们匹配片段内容方面又是宽松的。前几小节已经解释了控制保守程度的不同技术用默认值使路由匹配或多或少的片段、可选变量等等。

现在,到了考查宽松机制的时候了,看看我们如何控制匹配URL片段内容方面的宽松机制即,如何限制一条路由将要匹配的一组URL。一旦我们有了路由行为这两方面(指保守和宽松两方面译者注)的控制,我们就可以生成具有激光般精度的URL方案。

用正则表达式约束路由

我们要考查的第一项技术是用正则表达式约束路由。清单11-20含有一个例子。

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

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

我们通过把约束传递给MapRoute方法的办法定义约束。像默认值一样,约束被表示成一个匿名类型,在这里,类型属性对应于我们想要进行约束的片段变量名。

在这个例子中,我们使用了一个带有正则表达式的约束,它只匹配controller变量的值以H字母打头的URL

注:Default值是在约束被检查之前使用的。因此,如果我们请求的地址是/controller的默认值会被运用,这里是Home。约束然后被检查,而由于controller的值以H打头,这个默认的URL与这条路由是匹配的。

将一条路由约束到一组特定的值

我们可以用正则表达式来约束一条路由,以便对于一个URL片段,只有指定的值才会形成匹配。我们用竖线字符(|)来做这件事,如清单11-21所示。

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

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

这个约束将允许这条路由只匹配action片段的值是IndexAboutURL。把约束合在一起,那么就是,影响action变量值的约束与影响controller变量的约束相结合。这意味着,清单11-21所示的路由将只匹配这样的URLcontroller变量以H字母打头,而且action变量是IndexAbout。因此,现在你可以明白我们所说的生成十分精确的路由的含义。

HTTP方法约束路由

我们可以约束路由,以使它们匹配只有用指定的HTTP方法进行请求的URL,如清单11-22所示。

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

public  static  void RegisterRoutes(RouteCollection routes) {
    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 "});
}

指定HTTP方法约束的格式有点奇怪。它与我们为此属性所给出的名字无关,只要我们给它赋一个HttpMethodConstraint类的实例。在上述清单中,我们把这个约束属性叫做httpMethod,以帮助我们区分它与前面定义的那些基于值的约束。

注:借助HTTP方法约束路由的能力与使用诸如HttpGetHttpPost属性限制动作方法的能力是无关的。在请求管道中,路由约束的处理要早得多,而且它们决定了需要对一个请求进行处理的controlleraction的名字。动作方法属性用来确定由控制器用哪一个特定的动作方法对一个请求进行服务。我们在第14章将提供如何处理不同种类的HTTP方法(包括不太常用的PUTDELETE)的细节。

我们把希望支持的HTTP方法以字符串参数传递给HttpMethodConstraint类的构造器。在上述清单中,我们把这条路由限制到GET请求,但我们可以很容易地添加对其它方法的支持,像这样:

...
httpMethod =  new HttpMethodConstraint( " GET "" POST ") },
...

单元测试:路由约束

当测试约束路由时,重要的是对所要匹配的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 ");
}

我们的辅助方法也包括了对测试HTTP方法约束的支持。我们只需要把我们想要测试的HTTP方法作为参数传递给TestRouteMatchTestRouteFail方法,像上述测试方法一样,来测试清单11-21所定义的路由:

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

如果你不指定一个HTTP方法,该辅助方法默认为GET方法。

定义一个自定义约束

如果标准约束不满足你的需求,你可以通过实现IRouteConstraint接口的办法,定义你自己的自定义约束。清单11-23演示了一个自定义约束,它针对用户代理信息进行操作,该代理信息是由浏览器作为请求的一部分所提供的。

Listing 11-23. 创建一个自定义的路由约束

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( HttpContextBase httpContext,  Route route,  string parameterName,
                   RouteValueDictionary values,  RouteDirection routeDirection) {
             return httpContext.Request.UserAgent !=  null&&
            httpContext.Request.UserAgent.Contains(requiredUserAgent);
        }
    }
}

IRouteConstraint接口定义了Match方法,一个实现可以用该方法向路由系统指示,它的约束是否已经得到满足。Match方法的参数提供对以下对象的访问:客户端请求、进行评估的路由、约束的参数名、从URL提取的片段变量、以及请求是否要检查是输入还是输出URL的细节。例如,这里我们检查客户端请求的UserAgent属性的值,以看看它是否包含一个传递给我们约束构造器的值。清单11-24显示了我们用在一条路由中的自定义约束。

Listing 11-24. Applying a Custom Constraint in a Route

public  static  void RegisterRoutes(RouteCollection routes) {
    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 "});
}

在此清单中,我们已经约束了这条路由,以使它只与用户代理含有IE字符串的浏览器发出的请求匹配,这包括了从微软浏览器发出的请求。

注:我们要明白,这只是一种我们得到字母的办法,我们不建议限制你的应用程序,以使它只支持一种浏览器。我们很少使用用户代理字符串来演示自定义路由约束,而信所有浏览器有同等的机会。我们很讨厌web网站把他们对浏览器的偏爱强加到用户头上。

路由对磁盘文件的请求

并不是MVC应用程序的所有请求都是对controlleraction的。我们还需要一种对内容进行服务的办法,如图像、静态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 >

路由系统提供了对这种内容进行服务的集成支持。如果你启动应用程序,并请求/Content/StaticContent.html,你会在看到这个例子HTML文件的内容显示在浏览器中,如图11-6所示。

图11-6

图11-6. 请求静态内容文件

 

默认地,路由系统在评估应用程序的路由之前,会查看一个URL是否匹配一个磁盘文件。如果有一个匹配,那么这个磁盘文件就会被服务,于是就不会使用路由。我们可以通过把RouteConllectionRouteExistingFiles属性(该属性意为,对存在文件进行路由译者注)设置为true来保留这种行为,以使你的路由在检查磁盘文件之前被评估。

Listing 11-26. Enabling Route Evaluation Before File Checking

public  static  void RegisterRoutes(RouteCollection routes) {
     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 "});
}

如果把这条语句放在你定义路由之后,虽然这样也会起作用,但惯例是把这条语句放在紧靠RegisterRoutes方法的顶部。一旦此属性被设置为true,我们就可以定义路由来匹配响应磁盘文件的URL,例如,清单11-27所示的路由。

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

public  static  void RegisterRoutes(RouteCollection routes) {
    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 "});
}

这条路由把Content/StaticeContent.htmlURL请求映射到Account控制器的LogOn动作方法上。我们添加了一条对路由的约束,这意味着它只匹配用户代理字符串含有IE的浏览器所发出的请求。(这只是一个人为的例子,以演示这些特性的组合;我们不建议你在一个真实的应用程序中做这种事!)

RouteExistingFiles属性被启用时,只在没有与请求匹配的路由时,磁盘文件才会被投递给客户端。对于我们的例子路由来说,这意味着,Internet Explorer浏览器用户将得到Account控制器的响应,而所有其它用户将看到静态内容。你可以在图11-7中看到映射的URL在起作用。

图11-7

图11-7. 用一条路由窃听一个磁盘文件的请求

 

对磁盘文件请求进行路由需要小心考虑,不仅仅是因为URL模式将像其它一样热心地匹配这些种类的URL。例如,一个对/Content/StaticContent.html的请求将被{controller}/{action}这样的URL模式匹配。除非你特别小心,否则你可能终止于一些异常奇怪的结果,并且降低了性能。因此,启动这一选项是非常不得已的。为了演示这种情况,我们在Content目录中生成了第二个文件,名为OtherStaticContent.html,并添加一条新路由到RegisterRoutes方法,如清单11-28所示。

Listing 11-28. Adding a Route to the RegisterRoutes Method

public  static  void RegisterRoutes(RouteCollection routes) {
    routes.RouteExistingFiles =  true;
    routes.MapRoute( " DiskFile ", " Content/StaticContent.html ",
         new {
            controller =  " Account ", action = " LogOn ",
        },
         new {
            customConstraint =  new UserAgentConstraint( " 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 =  new HttpMethodConstraint( " GET "" POST "),
            customConstraint =  new UserAgentConstraint( " IE ")
        },
         new[] {  " URLsAndRoutes.Controllers "});
}

当我们请求Content/OtherStaticContent.html这个URL时,我们的请求与我们在清单11-28中添加的MyNewRoute路由URL匹配,于是,目标控制器是Content,动作方法是OtherStaticContent.html。文件的URL具有两个片段的任何请求都会发生这种情况。当然,这种控制器或动作方法是没有的,于是用户会收到一个404 — 未找到的错误。

绕过路由系统

我们在上一小节演示的,设置RouteExistingFiles属性使路由系统更广泛。通常绕过路由系统的请求,现在会根据我们所定义的路由进行评估。

与这一特性对应的是,使路由系统少一些广泛性,并阻止URL被我们的路由评估的能力。我们通过使用RouteCollection类的IgnoreRoute方法来做这件事,如清单11-29所示。

Listing 11-29. Using the IgnoreRoute Method

public  static  void RegisterRoutes(RouteCollection routes) {
    routes.RouteExistingFiles =  true;
    routes.MapRoute( " DiskFile ", " Content/StaticContent.html ",
         new {
            controller =  " Account ", action = " LogOn ",
        },
         new {
            customConstraint =  new UserAgentConstraint( " 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 =  new HttpMethodConstraint( " GET "" POST "),
            customConstraint =  new UserAgentConstraint( " IE ")
        },
         new[] {  " URLsAndRoutes.Controllers "});
}

我们可以用{filename}这样的片段变量来匹配URL的范围。在这种情况下,URL模式将匹配任何两片段URL,第一个片段是Content,第二片段的内容有.html扩展名。

IgnoreRoute方法在RouteCollection中生成一个条目,在这里,路由处理句柄是StopRoutingHandler类的一个实例,而不是MvcRouteHandler。路由系统被硬编码来识别这个句柄。如果传递给IgnoreRoute方法的URL模式匹配,那么,后继的路由将不被评估,就像有一条常规路由被匹配时那样。因此,它遵循,对IgnoreRoute方法的调用放在哪儿是重要的这一规则。在清单11-27中,我们通过不对HTML文件的任何请求进行路由的办法,使用了这一特性,以最小化RouteExistingFiles属性的冲突。

  

 未完待续...

转载于:https://www.cnblogs.com/jeriffe/articles/2395655.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值