主要学习内容:
ASP.NET MVC中路由主要用途:
URL重写关注的是将一个URL映射到另一个URL,而路由关注的是如何将URL映射到资源。
路由和URL重写另一个重要的区别就是:路由也使用它在匹配传入URL使用到的映射规则来帮助生成URL,而URL重写只能用于传入请求的URL,而不能帮助生成原始的RUL。
在ASP.NET MVC Web应用程序Global.asax.cs文件中,Application_Start方法调用了一个名为RegisterRoutes的方法,该方法是用来控制路由的,包含在~/App_Start/RouteCOnfig.cs中。我们首先学习特性路由,所以删除该方法中的内容,修改为如下:
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapMvcAttributeRoutes();
//routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//routes.MapRoute(
// name: "Default",
// url: "{controller}/{action}/{id}",
// defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
//);
}
路由的核心工作是把一个请求映射到一个操作上
[Route("about")]
public ActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}
当收到URL为http://localhost:端口号/about的请求时,就会运行About方法,如果对于操作有多个URL就可以使用多个路由特性。例如:我们想让首页通过/、/home、/home/index这几个URL都可以访问,路由如下:
[Route("")]
[Route("home")]
[Route("home/index")]
public ActionResult Index()
{
return View();
}
对于简单的路由,非常适合刚才说的静态路由,但并不是每个URL都是静态的,如果需要在URL包含记录的ID,可以通过添加路由参数解决该问题:
[Route("person/{id}")]
public ActionResult Details(string id)
{
//Do some work
return View();
}
[Route("{year}/{month}/{day}")]
public ActionResult Index(string year,string month,string day)
{
//Do some work
return View();
}
下表展示了上面的代码定义的路由如何将特定的路由解析为路由参数
URL | 路由参数 |
---|---|
/2014/April/10 | year = “2014”; month=”April” day = “10” |
/a-b/c-d/e-f | year = “a-b”; month=”c-d” day = “e-f” |
上面的方法中特性路由会匹配任何分为三段的URL,可以任意的命名这些参数(支持字母数字字符和其他的字符),当收到请求时,路由解析请求的URL,将路由参数值放在一个字典中,路由参数名称作为键,根据位置对应的URL子节作为值。当特性路由匹配并运行操作方法时,模型绑定会使用路由的路由参数为同名的方法参数填充值。
很多时候一个控制器中的路由遵循的模式具有相似的路由模板:
public class HomeController : Controller
{
[Route("home/index")]
public ActionResult Index()
{
return View();
}
[Route("home/about")]
public ActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}
}
除了URL的最后一段,这些路由是相同的,所以此时可以在控制器类上定义路由,并添加一个action的特殊路由参数,他可以作为任意操作名称的占位符,action参数的作用相当于在每个操作方法上添加路由,并静态的输入操作名,他只是一种更方便的语法。
有时控制器中的某些操作具有与其他操作稍微不同的路由,此时,我们可以把通用的路由放在控制器类上,在具有不同的路由模式的操作上重写默认路由。在操作方法级别指定的路由特性会覆盖掉控制器级别指定的任何路由特性。如下面的,我们想在首页支持/home路由,在Index上重写路由
[Route("home/{action}")]
public class HomeController : Controller
{
[Route("home")]
[Route("home/index")]
public ActionResult Index()
{
return View();
}
public ActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}
}
注意:如果仍希望支持默认的控制器路由(home/index),在重写时,需要在操作上再次列出改控制器路由。如果Index操作上只写了一个路由特性(Route[“home”]),尽管控制器上有一个默认路由(home/{action}),也不能用过home/index来访问Index方法。
可以看到上面每个路由都是以home开头的,可以通过使用RoutePrefix,可以仅在一个地方指定路由以home/开头。必要时也可以进行覆盖,例如:除了支持/home和/home/index以外,我们还想支持/.使用~/作为路由模板的开头,路由前缀就会被忽略,下面的Index方法支持的URL有三种(/、/home和/home/index)
[RoutePrefix("home")]
[Route("{action}")]
public class HomeController : Controller
{
[Route("~/")]
[Route("")]
[Route("index")]
public ActionResult Index()
{
return View();
}
public ActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}
}
[Route("person/{id}")]
public ActionResult Details(int id)
{
//Do some work
return View();
}
对于这个路由,当收到/person/bob的请求时,id的值是什么?这是一个容易出错的问题,答案取决于这里指的是哪个id,是路由参数还是操作方法参数。前面也知道,路由中的路由参数会匹配任何非空值,因此,在路由中,路由参数id的值为bob,所以路由匹配。但是,当MVC尝试运行操作方法时,会看到操作方法id时int类型的,而路由参数中的值bob不能转换为一个int值,所以不能执行。那么如果想同时支持/person/bob和/person/1,并为每个URL运行不同的操作,这时我们尝试添加一个不同路由的方法重载。
[Route("person/{id}")]
public ActionResult Details(int id)
{
//Do some work
return View();
}
[Route("person/{name}")]
public ActionResult Details(string name)
{
//Do some work
return View();
}
仔细查看路由会发现一个问题,它们都只是路由参数,而我们知道,路由参数会匹配任何非空字符,所以两个路由都会匹配到/person/bob和/person/1。路由带有二义性。这时我们可以使用路由约束来进行控制,路由约束是一个条件,只有满足该条件时,路由才会匹配,在这里,我们只需要简单的一个int约束:
[Route("person/{id:int}")]
public ActionResult Details(int id)
{
//Do some work
return View();
}
[Route("person/{name}")]
public ActionResult Details(string name)
{
//Do some work
return View();
}
像这样放在路由模板中的约束叫做内联约束,可用的内联约束有很多;
名称 | 示例用法 | 描述 |
---|---|---|
bool | {n:bool} | Boolean值 |
datetime | {n:datetime} | DateTime值 |
decimal | {n:decimal} | Decimal值 |
double | {n:double} | Double值 |
float | {n:float} | Single值 |
guid | {n:guid} | Guid值 |
int | {n:int} | Int32值 |
long | {n:long} | Int64值 |
minlength | {n:minlength(2)} | String值,至少包含两个字符 |
maxlength | {n:maxlength(2)} | String值,包含不少过两个字符 |
length | {n:length(2)} {n:length(2,4)} |
String值,刚好包含连个字符; String值,包含两个三个或四个字符 |
min | {n:min(1)} | Int64值,大于或等于1 |
max | {n:max(3)} | Int64值,小于或等于3 |
range | {n:range(1,3)} | Int64值,1、2或者3 |
alpha | {n:alpha} | String值,只包含A-Z和a-z |
regex | {n:regex(^a+$)} | String值,只包含一个或者更多个字符的a’(^a+$模式的Regex匹配) |
除了路由约束,我们还可以为路由参数提供默认值,看下面的代码,这里有一个没有参数的Index方法:
[Route("home/{action}")]
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}
}
我们想通过这个(/home)URL来调用这个方法,然而根据路由模板home/{action},这是不能运行的,因为定义的路由只匹配包含两个段的URL,但是/home只包含一个段。前面介绍过可以在操作方法上重写路由,但是这看上去是在做重复的工作,我们更希望保留原来的路由,只是让Index成为默认的路由。路由API允许为参数提供默认值。
[Route("home/{action=Index}")]
{action=Index}这段代码为{action}参数定义了默认值。此时,默认情况下就允许匹配没有action参数的URL请求。就是说,现在该路由既可以匹配具有一个段的URL,也可以匹配具有两个段的URL。现在我们就可以使用URL /home来调用Index操作。
除了提供默认值,也可以让一个URL参数变成可选的参数,查看如下代码:
[RoutePrefix("contacts")]
public class ContactsController : Controller
{
[Route("index")]
public ActionResult Index()
{
return View();
}
[Route("details/{id}")]
public ActionResult Details(int id)
{
return View();
}
[Route("update/{id}")]
public ActionResult Update(int id)
{
return View();
}
[Route("delete/{id}")]
public ActionResult Delete(int id)
{
return View();
}
}
可以看出,大部分操作都接受一个id参数,但并不是所有操作都如此,我们没有为这些操作使用单独的路由,而是只是用了一个路由,并将id作为可选参数进行处理
[RoutePrefix("contacts")]
[Route("{action}/{id?}")]
public class ContactsController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Details(int id)
{
return View();
}
public ActionResult Update(int id)
{
return View();
}
public ActionResult Delete(int id)
{
return View();
}
}
我们可以提供多个默认值或可选值,下面代码为{action}参数提供了默认值:
[Route("{action=index}/{id?}")]
可选路由参数是默认值的特例,从路由的角度来看,将参数标记为可选参数与列出参数的默认值之间并没有太大的区别。可选参数只是一个特殊的默认值UrlParameter.Optional。
注意:上面示例除了让id成为可选参数,还可以将id的默认值设置为空字符串(id=)来让路由匹配买这两种方法有什么不同?
我们前面说过,框架会从URL中解析出路由参数的值并将解析后的内容放入一个字典中,当我们班一个参数标记为可选,并且在URL中没有提供值时,路由就不会在字典中添加条目。如果该值的默认值被设置为空字符串,那么路由值字典将添加一个键,为id,对应的条目为空字符串,在一些场合这些很重要,他可以让我们知道id有没有被指定和指定为空的区别。
现在定义如下两个路由,第一个路由为{action}参数提供了默认值:
[Route("contacts/{action=Index}/{id}")]
[Route("contacts/{action}/{id?}")]
如果传入一个URL为/contacts/bob的请求,哪一个路由会与之匹配?由于第一个路由为action参数提供了默认值,所以会匹配,此时{id}的参数是bob,或者它与第二个路由匹配将action参数设置为bob,这时候就会出现二义性。为避免这种二义性,只有当前参数后面的每个参数也定义一个默认值时(包括使用了默认值UrlParameter.Optional的可选参数),路由引擎才能使用当前参数的默认值。在本例中,如果为action参数提供了默认值,也就应该为id参数定义默认值或使其成为可选参数。