以路由控制URL

      至此为止,我们一直在使用ASP.NET MVC新项目随带的默认路由配置。现在我们将深入探讨路由系统,并学习如何创建应用程序的自定义路由,以确保URL既是用户友好又是搜索引擎可访问的。

      路由的全部内容都是关于URL以及如何将URL作为应用程序的外部输入的。当使用其他开发工具,如PHP、Web Form或是经典的ASP时,URL通常对应于磁盘上的物理文件。一个http://example.com/Products.aspx这样的URL会导致执行负责处理该请求的名为Products.aspx的文件。

      通过使用URL路由,ASP.NET MVC解除了URL与物理文件的耦合。路由提供了一种把无扩展名的URL映射到控制器动作的方式,让开发人员对URL方案有完全的控制。

      在本章中,我们将介绍路由概念及其与MVC应用程序的关系,还将简要介绍它们如何用于ASP.NET Web Form项目。我们将考察如何设计应用程序的URL方案,然后将这些概念运用于创建一个示例应用程序的路由。最后看看如何测试路由,以确保它们按预期工作。

一、介绍URL路由

    与将URL绑定到磁盘上的物理文件不同,ASP.NET MVC引入的URL路由的底层结构能够将URL映射到控制器动作,而无须在服务器上有物理文件作为URL的目标。在本节中,我们将考察新建MVC项目随带的默认路由的结构,以及这些路由是如何与控制器和动作的概念相关联的。    

1.默认路由

    当创建一个新的ASP.NET MVC应用程序时,默认的项目模板会在Global.asax文件中调用一个名称为RegisterRoutes的方法。该方法负责为应用程序配置路由,并定义了最初两条路由——一条忽略路由和一条遵循{controller}/{action}/{id}模式的默认路由,如下所示。

      路由是通过调用MapRoute方法而定义的,该方法有个过载。在这个例子中,默认路由是通过调用三个参数的过载来配置的。第一个参数是路由名("Default")。第二个参数是用来匹配URL的URL模式。本例中,URL模式被定义为具有三个片段——控制器、动作和ID。第三个参数是一个匿名类型,它为这些片段定义了默认值。

      如果用户访问http://example.com/users/edit/5这样的URL,这将匹配默认路由,因为它有三个片段,如图所示。

      在这个例子中,字符串users映射到controller参数,edit映射到actio,而5映射到id。由于这个URL显然与路由相匹配,MVC框架会尝试查找名称为UsersController的类、调用Edit方法,并为其id参数传递5。如果找不到控制器或动作,框架会产生一个404错误。

      添加到路由定义中的默认参数意味着URL不必精确地匹配三片段URL模式。如果指定了默认控制器Home以及默认动作Index,当控制器片段省略时,路由将默认控制器为HomeController。同样,如果动作片段未指定,则路由会默认寻找Index动作。Id参数的默认值UrlParameter.Optional,意指不论是否指定第三个片段,路由都可以被匹配。下表给出了几个能匹配默认路由的例子。

URL 路由参数 被选中的动作方法
http://example.com/Users/Edit/5 Controller=User, Action=Edit, id=5 UsersController.Edit(5)
http://example.com/Users/Edit Controller=User, Action=Edit UsersController.Edit()
http://example.com/Users Controller=User, Action=Index UsersController.Index()
http://example.com Controller=User, Action=Index HomeController.Index()

      在IgnoreRoute方法中,模式{resource}.axd/{*pathInfo}确保文件扩展名为.axd的任何URL不会被路由引擎所处理,这样才能确保任何自定义HTTP处理程序(其扩展名为.axd)以正确方式呗处理,而不会被路由引擎拦截。

2.入站与出站路由

      入站路由(Inbound Routing):将URL映射到控制器或动作及任何附加参数。

      出站路由(Outbound Routing):通过一组给定的路由数据(通常是控制器和动作)生成相应的URL。

     上图所示的入站路由描述了一个控制器动作的URL调用。HTTP请求进入ASP.NET管道,并通过ASP.NET MVC应用程序注册的路由进行发送。每个路由都有处理请求的机会,而匹配路由随后会指定被使用的控制器和动作。

二、设计URL模式(schema)

1.建立简单、整洁的URL

                    传统URL:http://example.com/eventmanagement/events_by_month.aspx?year=2011&month=4

      使用路由系统的URL:http://example.com/events/2011/04

      这种URL带来的好处是,其中的日期有了一种明确的层次格式。

2.建立可破解的URL

      在设计URL方案时,考虑最终用户为了改变所显示的数据要如何操纵或“破解”URL是有价值的。例如,也许可以合理地假设,从以下URL移去参数“04”,可能表示2011年发生的全部事件:

      http://example.com/events/2011/04

      同样的逻辑可以形成下表所示的更全面的路由列表。

URL 描述
http://example.com/events 显示全部事件
http://example.com/events/<year> 显示某年事件
http://example.com/events/<year>/<month> 显示某月事件
http://example.com/events/<year>/<month>/<day> 显示某日事件

      让URL模式具有这种灵活性是很棒的,但这可能会导致应用程序中具有大量潜在的URL。在建立应用程序视图时,你总是要改出相应的导航。记住,可能在各个页面上不必对每个可能的URL组合都包含一个链接。在用户试图破解URL并使其生效时,让用户有一些惊喜的发现反而是件好事。

      如果不希望用户破解,可考虑使用连接字符来替代斜线,如/events/2008-04-01。

3.使用URL参数区分请求

      让我们对此路由加以扩展,并允许按类别列出事件。从用户的观点来看,最有用的URL可能像这样:

http://example.com/events/aspnet-usergroup-meeting

      但现在有问题了!我们已经有了一个与/events/<something>形式匹配的路由,用来列出特定年、月、日的事件,那么现在如何用/events/<something>也匹配类别?第二个路由片段现在意味着完全不同的含义,这与现有的路由不协调了。如果把这种URL交给路由系统,它应该把这种参数可能作类别还是日期?
      幸运的是,ASP.NET MVC的路由系统允许我们运用条件,使用正则表达式来确保路由只与某个模式的参数相匹配就够了。这意味着我们可以只用一条路由,就能让/events/2011-01-01形式的请求传递给按日期显示事件的动作,而让/events/asp-net-mvc-in-action形式的请求传递给按类别显示事件的动作。

4.尽可能避免暴露数据库ID

       一个用于托管开发人员事件的网站可能会定义这样的URL:

http://example.com/events/87

      87是从数据库获得的每一个对象都有一个主键形式的唯一标识符,但是除了数据库管理员之外,数字87对任何人都毫无意义。因此,应该尽可能避免在URL中使用数据库生成的ID,尽量让它们有意义、可读、易于理解。

http://example.com/events/houstonTechFest2010

5.考虑添加多余信息

      如果必须在URL中使用数据库ID,可考虑添加除了使URL可读外没什么目的的附加信息。

http://example.com/events/houstonTechFest2010/session-87
http://example.com/events/houstonTechFest2010/session-87/an-introduction-to-mvc

--------------------------------------------------------------------

搜索引擎优化(SEO)

      当涉及网站的搜索引擎优化方面时,有必要提一提设计良好的URL的价值,在URL中放置一个相关的关键字提升搜索引擎排序。设计要点如下:

  • 为控制器和动作使用描述性的、简单的、普遍使用的单词。力求尽可能相关并使用可能用于所建页面的关键词。
  • 当在路由中包含文本参数时,用连接字符替换所有的空格符。
  • 去掉字符串参数中不重要的标点和不必要的文本。
  • 在URL可能的地方包含附加的、有意义的信息,如标题和描述等。

----------------------------------------------------------------------

三、在ASP.NET MVC中实现路由

      默认项目模板创建了两个默认路由,但你可以不接受这两个默认路由的限制,添加自己的路由,以实现完全自定义的URL模式。以下将对此加以演示,以一个简单的在线商店为例,实现几个路由。我们将考察如何创建简单、静态的路由,以及创建更复杂的使用参数的路由和全匹配路由。

1.在线商店的URL模式(重要)

路由号 URL 描述
1 http://example.com/ 首页,重定向到分类列表
2 http://example.com/privacy 显示包含网站私有策略的静态页面
3 http://example.com/products/<productcode> 显示相应产品代码的产品详情页面
4 http://example.com/products/<productcode>/buy 将相应产品添加到购物篮
5 http://example.com/basket 显示当前用户的购物篮
6 http://example.com/checkout 启动当前用户的结算过程

      注意:路由4中的URL不是设计给用户看的,它通过表单递交进行链接,在动作处理完成后会立即进行重定向,因而这种URL不会在地址栏中出现。

2.添加自定义静态路由

      路由1是由默认路由处理的。

      路由2,是一个纯静态路由,将http://example.com/privacy映射到HomeController的Privacy动作。

routes.MapRoute("privacy_policcy","privacy", new { controller="Home", action="Privacy"});

      警告:添加到路由表中的路由次序决定了查找匹配时的路由搜索顺序。这意味着,源代码中列出的路由,应当从带有最具体条件的最高优先级降低到最低优先级,或全匹配路由。

3.添加自定义的动态路由

      当有少量偏离一般规则的URL时,静态路由是有用的。如果路由包含与页面显示的数据相关的信息,就需要动态路由了。

      路由3和路由4是用两个路由参数实现的:

routes.MapRoute("Product", "products/{productCode}/{action}", new { controller="Catalog", action="Show"});

      两个占位符将匹配URL中用斜线分隔的片段,productCode参数是必需的,但action是可选的。如果action未指定,该路由会默认指向CatalogController上的Show动作,并传递productCode参数。详细代码如下。

public class CatalogController : Controller
    {
        private ProductRepository _productRepository = new ProductRepository();

        public ActionResult Show(string productCode)
        {
            var product = _productRepository.GetByCode(productCode);

            if (product == null)
            {
                return new NotFoundResult();
            }
            return View(product);
        }

    }

     实现一个执行时能生成HTTP 404的自定义动作结果:

public class NotFoundResult:ActionResult
    {
        public override void ExecuteResult(ControllerContext context)
        {
            context.HttpContext.Response.StatusCode = 404;
            new ViewResult { ViewName = "NotFound" }.ExecuteResult(context);
        }
    }
}

      NotFoundResult通过集成ActionResult,在其中提供了必须实现的ExecuteResult方法。该方法将响应状态码设置为404,然后渲染一个名为NotFound的视图,该视图位于Views/Shared目录。

      注意:HttpNotFoundResult动作,也将响应转台码设置为404,但它未提供显示自定义错误页面的机制,因此总是会给最终用户显现一个空屏。

      最后,我们可以添加模式中的路由5和路由6。

routes.MapRoute{"catalog", "{action}",
new { controller="Catalog" },
new { action=@"basket|checkout"});

      这些路由几乎是静态路由,只不过它们是用一个参数和一个路由约束来实现的,以保持较少的路由数目。这么做的主要原因有两个。第一,每个请求都必须扫描路表进行匹配,所以大的路由集合会影响到性能。第二,路由越多,路由优先级问题出现的风险也越高。较少数目的路由规则更易于维护。

      MapRoute方法的第四个参数包含了路由约束。约束参数是一个匿名类型形式的字典,可以用于指定如何约束特定的路由参数。在本例中,我们使用了一个正则表达式来指明,仅当片段字符串为“basket”或“checkout”时,才匹配action参数。这种约束能够适当地阻止把未知动作传递给控制器。

4.全匹路由

      我们现在已经添加了静态和动态路由,以便为网站的不同URL提供内容。但假设有一个与所有路由都不匹配的请求,会发生什么?结果会抛出一个异常,这是实际应用程序中不希望发生的事情。为了对此加以处理,我们可以使用与ASP.NET的错误处理基础架构结合在一起的全匹配路由。

      我们将添加一个全匹配路由,用它匹配尚未被其他路由匹配的任何URL,显示HTTP404的错误消息,它应该是最后一条被定义的路由。

routes.MapRoute("404-catch-all", "{*catchall}",
new { controller="Error", action="NotFound"});

     值catchall为全匹配路由要拾取的值提供了一个名称。与规则路由参数不同,全匹配参数(以星号为前缀)会捕获包括正斜线在内的整个URL部分,正斜线通常用于分隔路由参数。在上述示例中,该路由被映射到ErrorController的NotFound动作。

public class ErrorController: Controller
{
    public ActionResult NotFound()
    {
        return new NotFoundResult();
    }
}

      现在,可以删去默认的{controller}/{action}/{id}路由,因为我们已经完全定制了路由,以匹配我们的URL模式。或者,你也许会选择保留它,以作为访问其他控制器的一种默认方式。

四、使用路由系统生成URL

    每当网站中需要一个URL时,我们都要求框架给出,而不是采用硬编码。我们需要制定一种控制器、动作以及参数的组合,剩下的由ActionLink方法完成。ActionLink是MVC框架中HtmlHelper类上的一个扩展方法,它会生成一个插入了正确URL的完整的HTML<a>元素,该URL与传递进来的对象参数所指定的路由相匹配。以下是调用ActionLink的一个例子:

@Html.ActionLink ( "MVC3 in Action", "Show", "Catalog", 
new { productCode = "mvc-in-action" }, null )

       第一个是超链接的显示文本;第二个和第三个指定了要被链接到的动作和控制器;第四个采用了一个匿名类型形式的字典,以指定任意的附加路由参数;最后一个是仍以匿名类型形式指定的任意的附加HTML属性。

      使用前面定义的路由,这个例子会生成一个链接,指向CatalogController上的Show动作,并带有为productCode指定的附加参数。以下是其输出:

<a href="/products/mvc-in-action">MVC3 in Action</a>

      类似地,如果使用HtmlHelper的BeginForm方法来建立表单标签,它会为你生成URL。有时,能够将路由部分未指定的参数传递给动作时有用的:

@Html.ActionLink ( "MVC3 in Action", "Show", "Catalog", 
new { productCode = "mvc-in-action", currency="USD" }, null )

      如果该参数与路由中的某个部分匹配,它将成为URL的一部分。否则,它将被附加到查询字符串。比如,以下是上述代码生成的链接:

<a href="/products/mvc-in-action?currency=USD">MVC3 in Action</a>

       在使用ActionLink时,被选中的路由是路由集合中所定义的第一个匹配路由。大多数情况下,这是足够的,但如果你希望请求一条特定的路由,可以使用RouteLink,它接受一个标识被请求路由的参数,像这样:

@Html.RouteLink ( "MVC3 in Action", "Show", "Catalog", 
new { productCode = "mvc-in-action" }, null )

      这个代码将查找一个带有product名称的路由,而不是指定的控制器和动作。

      有时候你需要获得一个URL,但不是为了链接或表单。这通常发生在编写Ajax代码需要设置一个请求URL时。UrlHelper类能够直接生成URL,由ActionLink方法和其他方法所使用。以下是一个例子:

@Url.Action ( "Show", "Catalog", 
new { productCode = "mvc-in-action" } )

      这个代码也返回/products/mvc-in-action,但没有任何包围标签。

 

posted @ 2014-12-01 11:23  liesl  阅读(4012)  评论(0编辑  收藏  举报