本文章将关注定义路由,并使用它们去处理URL,使用户能够到达控制器和动作。
文章非常长,可以对路由机制有较初步的了解。首先创建示例项目,项目名为UrlAndRoutes,如下图所示:
然后是创建示例控制器和示例视图,有三个控制器,分别为Admin控制器,Home控制器,Customer控制器,一个命名为ActionName示例视图。这三个控制器都返回ActionName视图。代码如下图所示:
namespace UrlsAndRoutes.Controllers
{public class AdminController : Controller{// GET: Adminpublic ActionResult Index(){ViewBag.Controller = "Admin";ViewBag.Action = "Index";return View("ActionName");}}
}
namespace UrlsAndRoutes.Controllers
{public class HomeController : Controller{// GET: Homepublic ActionResult Index(){ViewBag.Controller = "Home";ViewBag.Action = "Index";return View("ActionName");}}
}
namespace UrlsAndRoutes.Controllers
{public class CustomerController : Controller{// GET: Customerpublic ActionResult Index(){ViewBag.Controller = "Customer";ViewBag.Action = "Index";return View("ActionName");}public ActionResult List(){ViewBag.Controller = "Customer";ViewBag.Action = "List";return View("ActionName");}}
}
ActionName.cshtml视图的代码很简单,就是返回调用它的control和ActionName的名称,如下图所示:
@{Layout = null;
}<!DOCTYPE html><html>
<head><meta name="viewport" content="width=device-width" /><title>ActionName</title>
</head>
<body><div> the control is @ViewBag.Controller</div><div> the action is @ViewBag.Action</div>
</body>
</html>
测试示例代码,启动默认路由,显示页面没有问题,如下图所示:
URL模式简介
路由系统由一组路由来实现它的功能。这些路由共同组成了应用程序的URL架构。这种URL架构是应用程序能够识别并能对之做出响应的一组URL。
URL可以分成几个片段。除主机名和查询字符串之外,这些URL的组成部分都用"/"字符进行分割,如下所示:
第一个片段含有单词“Admin”,第二个片段含有单词“Index”。很显然,第一个片段和控制器有关,第二个片段和动作有关。以下是做这件事的一个URL模式:{ controller } / { action }
当处理一个输入请求时,路由系统的工作是将这个请求URL与一个模式进行匹配,然后从此URL为这个模式中定义的片段变量提取出相应的值。片段变量用花括号 “ { ” 和 “ } ”字符表示。上述示例模式有两个片段变量,其名称分别为“controller”和“action”,因此,controller片段的值是Admin,而action片段的值是Index。
所谓“与一个模式”匹配是指,一个MVC应用程序通常会有几条路由,而路由系统会把输入URL逐一与每条路由的URL模式相比较,直到能找到一条匹配的路由为止。
默认情况下,一个URL模式将匹配具有正确片段的的任何URL。例如,模式{controller} / {action}将匹配任何具有两个片段的URL,如下图所示:
请求URL | 片段变量 |
http://mysite.com/Admin/Index | controller = Admin action = Index |
http://mysite.com/Index/Admin | controller = Index action = Admin |
http://mysite.com/Apples/Orange | controller = Apples action = Orange |
http://mystie.com/Admin | 不匹配,片段太少 |
http://mysite/Admin/Index/Soccer | 不匹配,片段太多 |
上图突出了URL模式的两个关键行为:
1、URL模式是保守的,因此只匹配与模式具有相同片段的URL。你可以从表中第四个,第五个例子看到这种情况(片段数不同就是不匹配)。
2、URL模式是宽松的,如果一个URL正好是具有正确的片段数,该模式就用来为片段变量提取值,而不管这个值是什么。
创建并注册一条简单路由
路由是在RouteConfig.cs文件中进行定义的,该文件位于项目的App_start文件夹中。 代码如下图所示:
namespace UrlsAndRoutes
{public class RouteConfig{public static void RegisterRoutes(RouteCollection routes){routes.IgnoreRoute("{resource}.axd/{*pathInfo}");routes.MapRoute(name: "Default",url: "{controller}/{action}/{id}",defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional });}}
}
先删掉上图中RegisterRoutes方法中其他代码,以便更加关注重点,如下图所示:
namespace UrlsAndRoutes
{public class RouteConfig{public static void RegisterRoutes(RouteCollection routes){routes.IgnoreRoute("{resource}.axd/{*pathInfo}");routes.MapRoute("MyRoute","{controller}/{action}");}}
}
运行代码,可以看到如下报错界面:
但是如果导航到一个与{controller} / {action } 匹配的URL时,将看到正确显示。如下图,就导航到了/Home/Index:
定义默认值
当请求应用程序的默认值时,出现错误的原因是它不匹配已经定义的路由。前面说过,URL是保守的,他们只匹配指定片段的URL。改变这种行为的一个方式是使用默认值。修改RegisterRoutes方法如下图所示:
namespace UrlsAndRoutes
{public class RouteConfig{public static void RegisterRoutes(RouteCollection routes){routes.IgnoreRoute("{resource}.axd/{*pathInfo}");routes.MapRoute("MyRoute","{controller}/{action}",new { action = "Index"});}}
}
上述代码中为action片段提供了一个默认值,该路由也将匹配单片段URL。当处理单片段URL时,路由系统将从唯一的URL片段中提取controller的值,并对action变量使用默认值。于是,可以请求http://localhost:29802/home ,系统会自动调用home控制器上的Index动作方法。运行效果如下图所示:
当然,也可以更直接一点,对controller也赋默认值,代码如下图所示:
namespace UrlsAndRoutes
{public class RouteConfig{public static void RegisterRoutes(RouteCollection routes){routes.IgnoreRoute("{resource}.axd/{*pathInfo}");routes.MapRoute("MyRoute","{controller}/{action}",new {controller = "home", action = "Index"});}}
}
这就是为什么刚新建了一个项目,没有任何片段 就导航到home控制器下的Index动作的原因,因为RegisterRoute方法里默认控制器是home,动作是Index,运行效果如下图所示:
使用静态URL片段
并不是一个URL模式中的所有片段都需要是可变的。也可以创建具有静态片段的模式。假设希望匹配一下这种URL,以支持带有public前缀的URL:http://localhost:29802/Public/Home/Index 可以通过修改如下代码来实现这个效果:
public class RouteConfig{public static void RegisterRoutes(RouteCollection routes){routes.IgnoreRoute("{resource}.axd/{*pathInfo}");routes.MapRoute("MyRoute","public/{controller}/{action}",new {controller = "home", action = "Index"});}}
这个全新的URL模式将只匹配含有三个片段的URL,第一个必须是public,其他两个片段含有任何值,并将被用于controller和action变量。如果省略后两个片段,那么将使用默认值。 运行效果如下图所示:
还可以创建既有静态变量也有可变元素片段的URL模式,如下图代码所示:
public static void RegisterRoutes(RouteCollection routes){routes.IgnoreRoute("{resource}.axd/{*pathInfo}");routes.MapRoute("", "X{controller}/{action}");//第一条路由routes.MapRoute("MyRoute","public/{controller}/{action}",//第二条路由new {controller = "home", action = "Index"});}
第一条路由的模式是匹配任意两个片段URL,而第一个片段是以“X”打头。用于controller的值取自第一个片段除“X”以外的部分,Action的值取自第二个片段。如果启动程序并到导航到 /XHome/Index ,可以看到如下图所示:
注意:路由系统根据最先定义的路由模式来匹配一个输入URL,并且只有在不匹配的时候,才会继续对下一条路由进行处理。路由依次被尝试,直到找出匹配的一条,或这组路由被尝试完。所以,必须首先定义教具体的路由。如果将上图中两条路由改变定义的顺序,那么X{controller}这条路由将永远无法到达,路由系统回去找 名为“XHome” 的控制器,因为这个控制器是不存在的,所以会报404——未找到的错误。
可以结合静态变量片段和默认值为特定的路由创建一个别名。如果已经公开发布了URL方案,而且它与你的用户形成了一种契约,那么,创建这种别名可能是有用的。如果在这种情况下(指已经于用户形成契约)重构应用程序,则需要保留以前的URL格式。设想以前用的是一个Shop控制器,现在要有Home控制器来代替。修改代码,下图演示了如何才能创建一个保留旧式URL方案的路由。
public static void RegisterRoutes(RouteCollection routes){routes.IgnoreRoute("{resource}.axd/{*pathInfo}");routes.MapRoute("ShopSchem","Shop/{action}", //新增路由new { controller = "Home" });routes.MapRoute("", "X{controller}/{action}");routes.MapRoute("MyRoute","public/{controller}/{action}",new {controller = "home", action = "Index"});}
添加这条路由匹配第一个片段是“Shop”的任意两片段URL,action的值取自第二个URL片段。这个URL模式不含controller的可变片段,因而会使用所提供的默认值。这意味着对Shop控制器上一个动作请求会被转换成Home控制器的请求,启动程序,并导航到/Shop/Index 网址。如下图所示:
而且可以更进一步,被重构且不再出现在控制器中的动作方法创建别名,为此,只要简单的创建一个静态URL,并提供controller和action的默认值,如下图所示:
{routes.IgnoreRoute("{resource}.axd/{*pathInfo}");routes.MapRoute("ShopSchem2", "Shop/OldAction", //新增路由new { controller = "Home",action = "Index"});routes.MapRoute("ShopSchem","Shop/{action}", new { controller = "Home" });routes.MapRoute("", "X{controller}/{action}");routes.MapRoute("MyRoute","public/{controller}/{action}",new {controller = "home", action = "Index"});}
再一次提醒,要注意放置新路由的位置,以使它被首先定义。这是因为新路由比后面的路由更具体。例如,如果一个对Shop/OldAction 的请求被下一条路由定义来处理,就会得到与想要的不同的结果。这个请求将被处理成一个“404——未找到”错误,并注意,路由名称必须唯一。运行结果,如下图所示:
定义自定义片段变量
contorller和action片段变量对MVC框架而言有特殊的含义,显然,它们对应于请求进行服务的控制器和动作方法。 但是也可以自己定义变量。修改代码如下图所示:
public static void RegisterRoutes(RouteCollection routes){routes.IgnoreRoute("{resource}.axd/{*pathInfo}");routes.MapRoute("MyRoute", "{controller}/{action}/{id}",new { controller = "Home",action = "Index",id = "DefaultId"});}
该路由的URL模式定义了标准的controller 和 action 变量,以及一个名为“id”的自定义变量。这条路由将匹配任何0~3个片段的URL。第三个片段的内容将被赋值给id变量,而且如果没有第三个片段,将采用默认值。
通过使用RouteData.Values属性,能够在一个动作方法中访问任何一个片段变量。如下图所示,对Home控制器添加了以一个名为“CustomVariable”的动作方法:
namespace UrlsAndRoutes.Controllers
{public class HomeController : Controller{// GET: Homepublic ActionResult Index(){ViewBag.Controller = "Home";ViewBag.Action = "Index";return View("ActionName");}public ActionResult CustomVariable(){ViewBag.Controller = "Home";ViewBag.Action = "CustomVariable";ViewBag.CustomVariable = RouteData.Values["id"];return View();}}
}
并添加CustomVariable.cshtml视图与之匹配,代码如下图所示:
@{Layout = null;
}<!DOCTYPE html><html>
<head><meta name="viewport" content="width=device-width" /><title>CustomVariable</title>
</head>
<body><div> The controller is : @ViewBag.Controller</div><div> The action is @ViewBag.Action</div><div> The custom varibale is @ViewBag.CustomVariable</div>
</body>
</html>
运行程序,并导航到 /Home/CustomVariable/Hello 网址,结果如下图所示:
以为给变量id提供了一个默认值DefaultId,所以导航到 /Home/CustomVariable 网址,结果如下图所示:
用自定义变量作为动作方法参数
使用RouteData.Values属性只是访问自定义路由变量的一种方式。另一种方式要优雅的多。如果以URL模式中的变量相匹配的名称,来定义动作方法的参数,MVC框架将把从URL获得的值作为参数传递给该动作方法。 代码如下图所示:
namespace UrlsAndRoutes.Controllers
{public class HomeController : Controller{// GET: Homepublic ActionResult Index(){ViewBag.Controller = "Home";ViewBag.Action = "Index";return View("ActionName");}public ActionResult CustomVariable(string id){ViewBag.Controller = "Home";ViewBag.Action = "CustomVariable";ViewBag.CustomVariable = id;return View();}}
}
当路由系统根据上图所定义的路由来匹配一个URL时,URL中第三个片段的值被赋值给了自定义变量id。MVC框架会将片段变量列表与动作方法参数列表进行比较,如果名称匹配,便将URL的值传递给该方法。
这里将id参数定义成一个string,但MVC框架会尝试将URL的值转化为所定义的任何参数类型,如果将id参数声明为int 或者 DateTime,那么,从URL模式接收到的将是一个被解析成该类型示例的值。
定义可选URL片段
可选URL片段是指,用户不需要指定,但又未指定默认值的片段。通过将默认值设置为“UrlParameter.Optional”,便指明了一个片段变量是可选的,如下图所示:
public class RouteConfig{public static void RegisterRoutes(RouteCollection routes){routes.IgnoreRoute("{resource}.axd/{*pathInfo}");routes.MapRoute("MyRoute", "{controller}/{action}/{id}",new { controller = "Home", action = "Index", id = UrlParameter.Optional});}}
以上的路由将匹配不管是否提供id的URL,下表演示了它对不同URL的工作方式:
片段 | 示例URL | 映射成 |
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 | 不匹配,片段太多 |
由上表可见,只有当输入URL中存在相应片段时,id变量才会被添加到变量集合中。 在下图代码中,对控制器做了修改,以相应无对应值的id片段变量。
namespace UrlsAndRoutes.Controllers
{public class HomeController : Controller{// GET: Homepublic ActionResult Index(){ViewBag.Controller = "Home";ViewBag.Action = "Index";return View("ActionName");}public ActionResult CustomVariable(string id){ViewBag.Controller = "Home";ViewBag.Action = "CustomVariable";ViewBag.CustomVariable = id ?? "<no value>";return View();}}
}
运行效果如下图所示:
使用可选URL片段强制关注分离
如果开发人员十分注重MVC模式中的关注分离,他们不喜欢将片段变量的默认值放在应用程序的路由中。如果是因为这样,可以使用C#中的可选参数,以及路由中的可选片段变量,来定义动作方法的参数的默认值。
public class HomeController : Controller{// GET: Homepublic ActionResult Index(){ViewBag.Controller = "Home";ViewBag.Action = "Index";return View("ActionName");}public ActionResult CustomVariable(string id = "DefaultId")//可选参数{ViewBag.Controller = "Home";ViewBag.Action = "CustomVariable";ViewBag.CustomVariable = id;return View();}}
以上代码和下面的路由是等价的:
routes.MapRoute("MyRoute", "{controller}/{action}/{id}",new { controller = "Home", action = "Index", id = "DefaultId" });
定义可变长路由
改变URL模式默认保守性的另一种方式是接收可变数目的URL片段。这让你能够以一个单一的路由,对任意长度的URL进行路由。通过指定一个叫做“全匹配(catch)”的片段变量,并以星号(*)作为其前缀,便可以定义对可变片段数的支持。代码如下图所示:
public class RouteConfig{public static void RegisterRoutes(RouteCollection routes){routes.IgnoreRoute("{resource}.axd/{*pathInfo}");routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",new { controller = "Home", action = "Index", id = UrlParameter.Optional });}}
现在,这条路由将匹配任何URL,无论URL包含多少个片段数,也不管这些片段的值是什么。前三个片段分别用于设置controller、action和id变量的值。如果URL含有更多的片段,则它们全部被赋给catchall 变量。如下图所示:
片段数 | 示例URL | 映射成 |
0 | / | controller = Home action = Index |
1 | /Customer | controller = Customer action = Index |
2 | /Customer/List | controller = Customer action = List |
3 | /Customer/List/All | controller = Customer action = List id = All |
4 | /Customer/List/All/Delete | controller = Customer action = List id = All catchall = Delete |
5 | /Customer/List/All/Delete/Perm | controller = Customer action = List id = All catchall = Delete/Perm |
以上路由中的URL模式所匹配的片段数量是没有上限的。注意:有catchall捕获的片段是以“片段/片段/片段”的形式表示的。你要对这个字符串进行处理,把它分解为一个个片段。
按命名空间区分控制器优先顺序
当一个输入请求URL与一条路由进行匹配时,MVC框架取得controller变量的值,并查找相应的控制器的名称。例如,当controller变量名称是“Home”时,那么,MVC框架会查找名称为“HomeController”的控制器。这是一个不合格的类名,如果两个或者多个名为“HomeController”的控制器,MVC框架将不知道怎么做。
为了测试,在项目的根目录下创建一个名为AdditionalControllers的文件夹,并添加一个Home控制器,如下图所示:
namespace UrlsAndRoutes.AdditionalControllers
{public class HomeController : Controller{// GET: Homepublic ActionResult Index(){ViewBag.Controller = "Additional Controllers --- Home";ViewBag.Action = "Index";return View("ActionName");}}
}
运行程序,会发现报了如下错误:
很明显可以看到是错误提示是控制器重复了,这个问题比想象的更会经常出现,尤其是在一些大的MVC项目中, 遇到命名冲突只是时间问题。为了解决这一问题,可以告诉MVC框架,在试图解析控制器名称的时候,对某些命名空间给予优先处理,如下图所示:
namespace UrlsAndRoutes
{public class RouteConfig{public static void RegisterRoutes(RouteCollection routes){routes.IgnoreRoute("{resource}.axd/{*pathInfo}");routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",new { controller = "Home", action = "Index", id = UrlParameter.Optional},new[] { "UrlsAndRoutes.AdditionalControllers" });}}
}
上述代码中把命名空间表示成一个字符串数组,告诉MVC框架,在考察其他命名空间之前,先考察UrlsAndRoutes.AdditionalControllers 命名空间。
如果在这个命名空间中找不到合适控制器,那么MVC框架会默认回到正常行为,并考察所有可用的命名空间。 运行结果如下图所示:
注意:添加到一条路由的命名空间具有同等的优先级,MVC框架不会先检查第一命名空间,然后第二、第三命名空间。也就是说,同一个条路由中的命名空间不是按顺序进行检查的,而是会被同等对待的。如下图中,如果把两个项目的命名空间都加到这条路由里来,还是会报错:
routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional},
new[] { "UrlsAndRoutes.AdditionalControllers" , "UrlsAndRoutes.Controllers" });
如果希望对一个命名空间的某个控制器给与优先级,但是又要解析另一个命名空间中的所有其他控制器,就需要创建多条路由,如下图所示:
routes.MapRoute("AddControllerRoute", "Home/{controller}/{action}/{id}/{*catchall}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new[] { "UrlsAndRoutes.AdditionalControllers" });routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional},
new[] { "UrlsAndRoutes.Controllers"});
当用户明确的请求一个片段为Home的URL时,会运用第一条路由,并且会以AdditionalControllers文件夹中的Home控制器为目标。所有的其他请求,包括未指定第一片段的那些请求,会以controllers文件夹中的控制器去处理。
也可以告诉MVC框架,只考察指定的命名空间。如果找不到一个匹配的控制器,那么框架不会搜索其他地方。代码如下所示:
Route myRoute = routes.MapRoute("AddControllerRoute", "Home/{controller}/{action}/{id}/{*catchall}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new[] { "UrlsAndRoutes.AdditionalControllers" });myRoute.DataTokens["UseNamespaecFallback"] = false;//禁止搜索其他命名空间
用则表达式约束路由
public class RouteConfig{public static void RegisterRoutes(RouteCollection routes){routes.IgnoreRoute("{resource}.axd/{*pathInfo}");routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",new { controller = "Home", action = "Index", id = UrlParameter.Optional},new { controller = "^C.*"},new[] { "UrlsAndRoutes.Controllers"});}}
以上代码用正则表达式形成了一个约束,它只匹配controller变量值已“C”字母的大头的URL。
默认值是是在约束检查之前运用的,因此,如果请求的URL是“/”,会将以默认值“Home”运用于controller,然后才会检查约束。而且,如果此时controller的值是以“C”,则会报错,如下图所示:
如果把约束改成以H开头,则运行正常:
将一条路由约束到一组指定的值
可以用正则表达式来约束一条路由,以便对于一个URL片段,只有指定的一些值才能形成匹配。可以用竖线“|”字符来做这件事。代码如下图所示:
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 片段的值是 “Index” 或 “About”的URL,controller的值必须是H打头。
使用HTTP方法约束路由
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")},//限制请求类型
new[] { "UrlsAndRoutes.Controllers"});
上述代码将这条路由限制到GET和POST请求。
使用类型和值约束
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"),
id = new RangeRouteConstraint(10,20)},//值约束
new[] { "UrlsAndRoutes.Controllers"});
在System.Web.Mvc.Routing.Constraints 命名空间的约束类中,检查片段变量是否是不同的C#类型值,并执行基本的检查。在上图中,使用了RangeRouteConstraint类,它检查提供给片段的变量的值是在两个边界之间的一个有效的int类型。下图描述了完整的约束集合。他们并不接受参数,由于它们将用于配置路由,所以仅显示类名,暂且忽略了属性约束列。
名称 | 描述 | 属性约束 |
AlphaRouteConstraint() | 匹配字母字符 主要是(A~Z,a~z) | alpha |
BoolRouteConstraint() | 匹配一个可以解析成bool类型的值 | bool |
DateTimeRouteConstraint() | 匹配一个可以解析成DateTime类型的值 | datetime |
DecimalRouteConstraint() | 匹配一个可以解析成deciaml类型的值 | decimal |
DoubleRouteConstraint() | 匹配一个可以解析成double类型的值 | double |
FloatRouteConstraint() | 匹配一个可以解析成float类型的值 | float |
IntRouteConstraint() | 匹配一个可以解析成int类型的值 | int |
LengthRouteConstraint(len) LengthRouteConstraint(min,max) | 匹配一个指定字符个数的值,或匹配字符个数在min和max之间的值 | length(len) length(min,max) |
LongRouteConstraint() | 匹配一个可以解析成long类型的值 | long |
MaxRouteConstraint(val) | 匹配一个值小于val的int值 | max(val) |
MaxLengthRouteConstraint(len) | 匹配一个长度不超过len的字符值 | maxlength(len) |
MinRouteConstraint(val) | 匹配一个值大于val的int值 | min(val) |
MinLengthRouteConstraint(len) | 匹配一个长度至少为len字符串 | minlength(len) |
RangeRouteConstraint(min,max) | 匹配一个值在min和max之间的值 | range(min,max) |
使用CompoundRouteConstraint类,该类接受一个约束数组作为它构造器的参数,可以为一个单一的片段变量组合不同的约束。如下图所示,同时将AlphaRouteConstraint 和 MinLengthRouteConstraint运用到id片段变量,以确保路由将仅匹配包含字母字符并且至少含有6个字符的字符串值:
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"),
id = new CompoundRouteConstraint(
new IRouteConstraint[] {new AlphaRouteConstraint(),
new MinLengthRouteConstraint(6) })},
new[] { "UrlsAndRoutes.Controllers"});
定义自定义约束
如果标准约束不满足你的需求,可以通过实现IRouteConstraint接口,来定义自己的自定义约束。在示例项目中添加一个Infrastructrue文件夹,并创建新的类UserAgentConstrainst.cs,如下所示:
namespace UrlsAndRoutes.Infrastructure
{public class UserAgentConstraint:IRouteConstraint{private string requiredUserAgent;public UserAgentConstraint(string agentParam){this.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还是输入的URL的细节。对于上述示例,要检查的是客户端请求的UserAgent属性的值,看它是否含有一个被传递给构造器的值。
namespace UrlsAndRoutes.Infrastructure
{public class UserAgentConstraint:IRouteConstraint{private string requiredUserAgent;public UserAgentConstraint(string agentParam){this.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);}}
}
并增加一条路由:
routes.MapRoute("ChromeRoute", "{*catchall}",
new { controller = "Home", action = "Index" },
new { customConstraint = new UserAgentConstraint("Chrome") },
new[] { "UrlsAndRoutes.AdditionalControllers" });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"),
id = new CompoundRouteConstraint(
new IRouteConstraint[] {
new AlphaRouteConstraint(),
new MinLengthRouteConstraint(6) })},
new[] { "UrlsAndRoutes.Controllers"});
第一条路由使他之匹配来自用户代理字符串含有Chrome的浏览器的请求,并指向UrlsAndRoutes.AdditionalControllers。该路由是有一个片段,意味着controller 和 action 总是取自默认值,而不是URL本身。
第二条路由将匹配其他所有请求,并以controller文件夹中的控制器为目标,这两条路由的情况是,有一种浏览器最终只能访问程序的同一个位置。第二条路由动用了约束,第三个片段只能包含6个或者以上的字母字符,以使第二个路由匹配。运行结果如下图所示: