# 七、路由

## 简介

路由是管理 web 应用程序 URL 的强大机制。它允许我们有好的，友好的网址，可以提供一个有用的见解，他们的意思是什么。除此之外，它还提供了不应该被丢弃的安全性和可扩展性特性。

## 路由

路由是 MVC 的一个重要部分，但实际上是在 ASP.NET 3.5 SP1 被引入到 Web Forms 中的。它依赖于全局 **Web.config** 文件中配置的一个 ASP.NET 模块( [UrlRoutingModule](https://msdn.microsoft.com/en-us/library/system.web.routing.urlroutingmodule.aspx) )，所以我们通常不需要担心它，还依赖于一个通过代码配置的路由表。我们不会详细讨论 ASP.NET 路由，但我们将探索它的两个可扩展性点:路由处理程序和路由约束。

### 路由处理程序

路由处理程序是一个实际负责处理请求的类，由 [IRouteHandler](https://msdn.microsoft.com/en-us/library/system.web.routing.iroutehandler%28v=vs.110%29.aspx) 接口的实现组成。ASP.NET 包含了一个 MVC 的实现([mvcrouthandler](https://msdn.microsoft.com/en-us/library/system.web.mvc.mvcroutehandler%28v=vs.118%29.aspx)，但是你可以很容易地为 Web Forms 推出你自己的实现。那么，它有什么用呢？那么，您可以向其中添加自己的代码逻辑，并放心它将只应用于指定的路由。让我们看一个例子:

代码示例 70

```cs
          var customRoute = new Route("{controller}/{action}/{id}", 
               new RouteValueDictionary(new { controller = "Home", action = "Index", 
               id = UrlParameter.Optional }), new MyRouteHandler());
          RouteTable.Routes.Add(customRoute);

```

我们正在注册一个普通的路由，唯一的区别是我们正在传递一个定制的路由处理程序， **`MyRouteHandler`** ，下面给出了它的一个示例实现:

代码示例 71

```cs
          class MyRouteHandler : MvcRouteHandler
         {
               protected override IHttpHandler GetHttpHandler(RequestContext 
                     requestContext)
               {
                     var tenant = TenantsConfiguration.GetCurrentTenant();
                     //do something with the current tenant
                     return base.GetHttpHandler(requestContext);
               }
         }

```

该处理程序将适用于所有匹配`“**{controller}/{action}/{id}**”,`给定网址模板的请求，无论当前租户是谁。通过调用它的基础实现，一切都将按预期运行。然而，我们可以根据某些条件来限制我们的路线——输入路线限制。

在我们继续之前，重要的是要注意这些例子是针对 MVC 的，但是我们可以为 Web Forms 做一些类似的事情；唯一不同的是基类 [PageRouteHandler](https://msdn.microsoft.com/en-us/library/system.web.routing.pageroutehandler%28v=vs.110%29.aspx) ，在本例中:

代码示例 72

```cs
          class PageRouteHandler : PageRouteHandler
         {
               public WebFormsRouteHandler(String virtualPath) : base(virtualPath) { }

               public override IHttpHandler GetHttpHandler(RequestContext 
                     requestContext)
               {
                     var tenant = TenantsConfiguration.GetCurrentTenant();
                     //do something with the current tenant
                     return base.GetHttpHandler(requestContext);
               }
         }

```

此外，注册略有不同:

代码示例 73

```cs
          var customRoute = new Route(String.Empty, new WebFormsRouteHandler
                 ("~/Default.aspx"));
         RouteTable.Routes.Add(customRoute);

```

### 路线限制

一个路由约束需要实现框架接口 [IRouteConstraint](https://msdn.microsoft.com/en-us/library/system.web.routing.irouteconstraint%28v=vs.110%29.aspx) 。当这样的实现应用于路由时，[urrlroutingmodule](https://msdn.microsoft.com/en-us/library/system.web.routing.urlroutingmodule.aspx)将首先检查它，看当前请求是否可以匹配到路由。下面是我们如何指定一个(或多个)路由约束:

代码示例 74

```cs
          var customRoute = new Route("{controller}/{action}/{id}", 
               new RouteValueDictionary(new { controller = "Home", action = "Index", 
               id = UrlParameter.Optional }), new RouteValueDictionary(
               new Dictionary<String, Object> { { "my", new MyRouteConstraint() } }),
               new MyRouteHandler());
          RouteTable.Routes.Add(customRoute);

```

最后，一个考虑到当前租户的约束示例:

代码示例 75

```cs
          class MyRouteConstraint : IRouteConstraint
         {          
               public Boolean Match(HttpContextBase httpContext, Route route, 
                     String parameterName, RouteValueDictionary values, 
                     RouteDirection routeDirection)
               {
                     var tenant = TenantsConfiguration.GetCurrentTenant();
                     //do some check with the current tenant and return true or false
                     return true;
               }
         }

```

## IIS 重写模块

基于当前租户限制访问的另一个选项在于 [IIS 重写模块](http://www.iis.net/downloads/microsoft/url-rewrite)。这个模块是从微软免费下载的，它允许我们指定控制访问和重定向请求的规则，所有这些都来自 **Web.config** 文件。基本上，重写规则包括:

![](img/image035.jpg)

图 14: IIS 重写模块规则组件

**`rule`** 元素定义了 **`name`** ，如果规则应该是 **`enabled`** 或者不是，并且模式匹配语法用在所有模糊表达式属性( **`patternSyntax`** )中，这可以是:

*   **通配符**:没有正则表达式，只有通配符(*)匹配
*   **ECMAScript** : ECMAScript 标准正则表达式
*   **精确匹配**:精确匹配

代码示例 76

```cs
          <rewrite>
                <rules>    
                      <rule enabled="true" patternSyntax="ECMAScript" 
                            name="ABC.COM only">
                            <!-- rest goes here -->
                      </rule> 
                </rules>   
          </rewrite>

```

然后是 **`match`** 元素。在其中我们指定一个 URL 模式( **`url`** )，可以是特定的页面，如果匹配应该以不区分大小写的方式进行( **`ignoreCase`** )，即使匹配应该被还原( **`negate`** )，也就是接受不匹配模式的 URL:

代码示例 77

```cs
          <rewrite>
                <rules>    
                      <rule enabled="true" patternSyntax="ECMAScript" 
                            name="ABC.COM only">
                            <match url="\/abc\.com\/.*" ignoreCase="true" />
                            <!-- rest goes here -->
                      </rule> 
                </rules>   
          </rewrite>

```

然后我们可以添加任意数量的**`conditions`**；这些通常通过对照以下匹配类型之一检查[服务器变量](https://msdn.microsoft.com/en-us/library/ms524602%28v=vs.90%29.aspx) ( **`input`** )来构建:

*   **`Pattern` :** 图案，默认
*   **`IsFile`** :检查文件是否存在
*   **`IsDirectory`** :检查目录是否存在

条件可以有不同的评价( **`logicalGrouping`** ):

*   **`MatchAll`** :所有规则都需要正面评价(默认)。
*   **`MatchAny`** :至少有一个规则是正的。

条件也可以被**否定**，如本例所示:

代码示例 78

```cs
          <rewrite>
                <rules>    
                      <rule enabled="true" patternSyntax="ECMAScript" 
                            name="ABC.COM only">
                            <match url="\/abc\.com\/.*" ignoreCase="true" />
                            <conditions>
                                  <add input="{HTTP_HOST}" matchType="Pattern" 
                                       pattern="abc.com" negate="true" />
                            </conditions>
                            <!-- rest goes here -->
                      </rule> 
                </rules>   
          </rewrite>

```

服务器变量或多或少是 web 服务器的标准；它们在 [RFC 3875](https://www.ietf.org/rfc/rfc3875) 中定义，包括:

*   **`HTTP_HOST`** :请求中发送的服务器主机名
*   **`HTTP_REFERER`** :浏览器来源的 URL
*   **`HTTP_USER_AGENT`** :用于访问页面的浏览器
*   **`HTTPS`** :请求是否使用 HTTPS
*   **`PATH_INFO`** :跟随实际服务器资源的路径
*   **`QUERY_STRING`** :请求的查询字符串
*   **`REMOTE_ADDR`** :客户地址
*   **`REMOTE_HOST`** :客户端的主机名
*   **`REMOTE_USER`** :认证用户

最后，我们指定一个**动作**，如果**匹配**和**条件**成功评估，则触发该动作。操作可以是以下之一:

*   **`AbortRequest`** :停止当前请求；用于拒绝访问
*   **`None`** :什么都不做
*   **`Rewrite`** :内部将请求改写为不同的东西
*   **`Redirect`** :将浏览器重定向到不同的资源
*   **`CustomResponse`** :发送自定义的 HTTP 头、状态码和子码

此示例拒绝除 **`abc.com`** 租户之外的所有人访问 **`/abc.com`** 下的所有内容:

代码示例 79

```cs
          <rewrite>
                <rules>    
                      <rule enabled="true" patternSyntax="ECMAScript" 
                            name="ABC.COM only">
                            <match url="\/abc\.com\/.*" ignoreCase="true" />
                            <conditions>
                                  <add input="{HTTP_HOST}" matchType="Pattern" 
                                       pattern="abc.com" negate="true"/>
                            </conditions>
                            <action type="AbortRequest" statusCode="401"
                                  statusDescription="Denied for others than ABC.COM" />
                      </rule> 
                </rules>   
          </rewrite>

```

最后，我们可以指定一个或多个服务器变量( **`serverVariables`** )。这些可以用于在条件中被引用，或者将某种信息传递给处理请求的处理程序。例如，如果我们要使用主机头策略捕获租户，我们可以:

代码示例 80

```cs
          <rewrite>
                <rules>    
                      <rule enabled="true" patternSyntax="ECMAScript" 
                            name="ABC.COM only">
                            <serverVariables>
                                  <set name="TENANT" value="{HTTP_HOST}" />
                            </serverVariables>
                            <match url="\/abc\.com\/.*" ignoreCase="true" />
                            <conditions>
                                  <add input="{TENANT}" matchType="Pattern" 
                                       pattern="abc.com" negate="true"/>
                            </conditions>
                            <action type="AbortRequest" statusCode="401"
                                  statusDescription="Denied for others than ABC.COM" />
                      </rule> 
                </rules>   
          </rewrite>

```

IIS 重写模块还具有一个捕获机制，允许我们检索匹配正则表达式的部分内容。例如，如果我们要使用查询字符串策略来确定租户，我们可以使用:

代码示例 81

```cs
          <conditions>
                <add input="{QUERY_STRING}" pattern="&amp;?tenant=(.+)" />
                <!-- the tenant passed in the query string will be in {C:1} -->
          </conditions>
          <action type="Rewrite" url="/tenant/{C:1}"/>

```

我们将使用 **`{R:*n*}`** 作为在 **`match`** 元素中捕获的引用，使用 **`{C:*n*}`** 作为在 **`conditions`** 中捕获的引用，其中 0 将返回整个匹配字符串以及在`“()”`中捕获的每个元素的 1，…，n。这个简单的示例将`“**tenant=something**”`的所有请求重定向到`“**/tenant/something**”.`例如，如果您想将某个租户的所有请求从一个资源重定向到另一个资源，即不应该被某个客户访问的资源，那么这个示例可能会很有用。