URL 路由

作者 :Erik Reitan

下载 Wingtip Toys 示例项目 (C#) 下载电子书 (PDF)

本教程系列将介绍使用 ASP.NET 4.5 和 Microsoft Visual Studio Express 2013 for Web 生成 ASP.NET Web Forms应用程序的基础知识。 本教程系列随附了一个包含 C# 源代码的Visual Studio 2013项目。

在本教程中,你将修改 Wingtip Toys 示例应用程序以支持 URL 路由。 通过路由,Web 应用程序可以使用友好、更易于记住且更受搜索引擎支持的 URL。 本教程以上一教程“成员身份和管理”为基础,是 Wingtip Toys 教程系列的一部分。

学习内容:

  • 如何为 ASP.NET Web Forms应用程序注册路由。
  • 如何向网页添加路由。
  • 如何从数据库中选择数据以支持路由。

ASP.NET 路由概述

URL 路由允许将应用程序配置为接受不映射到物理文件的请求 URL。 请求 URL 只是用户在浏览器中输入以查找网站上的页面的 URL。 使用路由来定义对用户具有语义意义的 URL,并有助于搜索引擎优化 (SEO) 。

默认情况下,Web Forms模板包括 ASP.NET 友好 URL。 大部分基本路由工作都将通过使用 友好 URL 来实现。 但是,在本教程中,你将添加自定义路由功能。

在自定义 URL 路由之前,Wingtip Toys 示例应用程序可以使用以下 URL 链接到产品:

https://localhost:44300/ProductDetails.aspx?productID=2

通过自定义 URL 路由,Wingtip Toys 示例应用程序将使用更易于阅读的 URL 链接到同一产品:

https://localhost:44300/Product/Convertible%20Car

路由

路由是映射到处理程序的 URL 模式。 处理程序可以是物理文件,例如Web Forms应用程序中的 .aspx 文件。 处理程序也可以是处理请求的类。 若要定义路由,请通过指定路由的 URL 模式、处理程序和(可选)名称来创建 Route 类的实例。

通过将 对象添加到 类的RouteTable静态Routes属性,将路由添加到Route应用程序。 Routes 属性是一个 RouteCollection 对象,用于存储应用程序的所有路由。

URL 模式

URL 模式可以包含文本值和变量占位符 (称为 URL 参数) 。 文本和占位符位于 URL 的段中,这些段由斜杠 (/) 字符分隔。

向 Web 应用程序发出请求时,URL 将分析为段和占位符,并将变量值提供给请求处理程序。 此过程类似于分析查询字符串中的数据并将其传递到请求处理程序的方式。 在这两种情况下,变量信息都包含在 URL 中,并以键值对的形式传递给处理程序。 对于查询字符串,键和值都在 URL 中。 对于路由,键是在 URL 模式中定义的占位符名称,只有值在 URL 中。

在 URL 模式中,通过将占位符括在大括号 ( {} ) 来定义占位符。 可以在一个段中定义多个占位符,但这些占位符必须用文本值分隔。 例如, {language}-{country}/{action} 是有效的路由模式。 但是, {language}{country}/{action} 不是有效的模式,因为占位符之间没有文本值或分隔符。 因此,路由无法确定将语言占位符的值与国家/地区占位符的值分开的位置。

映射和注册路由

在包含 Wingtip Toys 示例应用程序页面的路由之前,必须在应用程序启动时注册路由。 若要注册路由,需要修改 Application_Start 事件处理程序。

  1. Visual Studio 解决方案资源管理器 中,找到并打开 Global.asax.cs 文件。

  2. 将黄色突出显示的代码添加到 Global.asax.cs 文件,如下所示:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Optimization;
    using System.Web.Routing;
    using System.Web.Security;
    using System.Web.SessionState;
    using System.Data.Entity;
    using WingtipToys.Models;
    using WingtipToys.Logic;
    
    namespace WingtipToys
    {
        public class Global : HttpApplication
        {
            void Application_Start(object sender, EventArgs e)
            {
              // Code that runs on application startup
              RouteConfig.RegisterRoutes(RouteTable.Routes);
              BundleConfig.RegisterBundles(BundleTable.Bundles);
    
              // Initialize the product database.
              Database.SetInitializer(new ProductDatabaseInitializer());
    
              // Create custom role and user.
              RoleActions roleActions = new RoleActions();
              roleActions.AddUserAndRole();
    
              // Add Routes.
              RegisterCustomRoutes(RouteTable.Routes);
            }
    
            void RegisterCustomRoutes(RouteCollection routes)
            {
              routes.MapPageRoute(
                  "ProductsByCategoryRoute",
                  "Category/{categoryName}",
                  "~/ProductList.aspx"
              );
              routes.MapPageRoute(
                  "ProductByNameRoute",
                  "Product/{productName}",
                  "~/ProductDetails.aspx"
              );
            }
        }
    }
    

当 Wingtip Toys 示例应用程序启动时,它会调用 Application_Start 事件处理程序。 在此事件处理程序结束时, RegisterCustomRoutes 调用 方法。 方法 RegisterCustomRoutes 通过调用 MapPageRoute 对象的 方法 RouteCollection 添加每个路由。 路由是使用路由名称、路由 URL 和物理 URL 定义的。

第一个参数 (“ProductsByCategoryRoute”) 是路由名称。 它用于在需要时调用路由。 第二个参数 (“”Category/{categoryName}) 定义可基于代码动态的友好替换 URL。 使用基于数据生成的链接填充数据控件时,可以使用此路由。 路由如下所示:

routes.MapPageRoute(
      "ProductsByCategoryRoute",
      "Category/{categoryName}",
      "~/ProductList.aspx"
  );

路由的第二个参数包括由大括号 ({ }) 指定的动态值。 在这种情况下, categoryName 是一个变量,用于确定正确的路由路径。

注意

可选

通过将 方法移动到 RegisterCustomRoutes 单独的类,可能会发现管理代码更容易。 在 逻辑 文件夹中,创建一个单独的 RouteActions 类。 将上述 RegisterCustomRoutes 方法从 Global.asax.cs 文件移动到新 RoutesActions 类中。 RoleActions使用 类和 createAdmin 方法作为如何从 Global.asax.cs 文件调用 RegisterCustomRoutes 方法的示例。

你可能还注意到方法调用在RegisterRoutes事件处理程序的Application_Start开头使用 RouteConfig 对象。 此调用是为了实现默认路由。 使用 Visual Studio 的 Web Forms 模板创建应用程序时,它作为默认代码包含在内。

检索和使用路由数据

如上所述,可以定义路由。 添加到 Global.asax.cs 文件中事件处理程序的代码Application_Start将加载可定义的路由。

设置路由

路由要求你添加其他代码。 在本教程中,你将使用模型绑定来检索使用 RouteValueDictionary 数据控件中的数据生成路由时使用的对象。 对象 RouteValueDictionary 将包含属于特定产品类别的产品名称列表。 根据数据和路线为每个产品创建链接。

为类别和产品启用路由

接下来,将应用程序更新为使用 ProductsByCategoryRoute 来确定要为每个产品类别链接包含的正确路由。 你还将更新 ProductList.aspx 页面,以包含每个产品的路由链接。 链接将显示为更改前的原样,但是链接现在将使用 URL 路由。

  1. 解决方案资源管理器中,打开 Site.Master 页(如果尚未打开)。

  2. 使用黄色突出显示的更改更新名为“categoryList”的 ListView 控件,使标记如下所示:

    <asp:ListView ID="categoryList"  
        ItemType="WingtipToys.Models.Category" 
        runat="server"
        SelectMethod="GetCategories" >
        <ItemTemplate>
            <b style="font-size: large; font-style: normal">
            <a href="<%#: GetRouteUrl("ProductsByCategoryRoute", new {categoryName = Item.CategoryName}) %>">
                <%#: Item.CategoryName %>
            </a>
            </b>
        </ItemTemplate>
        <ItemSeparatorTemplate>  |  </ItemSeparatorTemplate>
    </asp:ListView>
    
  3. 解决方案资源管理器 中,打开 ProductList.aspx 页。

  4. ItemTemplate使用黄色突出显示的更新更新 ProductList.aspx 页面的元素,使标记如下所示:

    <ItemTemplate>
      <td runat="server">
        <table>
          <tr>
            <td>
              <a href="<%#: GetRouteUrl("ProductByNameRoute", new {productName = Item.ProductName}) %>">
                <image src='/Catalog/Images/Thumbs/<%#:Item.ImagePath%>'
                  width="100" height="75" border="1" />
              </a>
            </td>
          </tr>
          <tr>
            <td>
              <a href="<%#: GetRouteUrl("ProductByNameRoute", new {productName = Item.ProductName}) %>">
                <%#:Item.ProductName%>
              </a>
              <br />
              <span>
                <b>Price: </b><%#:String.Format("{0:c}", Item.UnitPrice)%>
              </span>
              <br />
              <a href="/AddToCart.aspx?productID=<%#:Item.ProductID %>">
                <span class="ProductListItem">
                  <b>Add To Cart<b>
                </span>
              </a>
            </td>
          </tr>
          <tr>
            <td>&nbsp;</td>
          </tr>
        </table>
        </p>
      </td>
    </ItemTemplate>
    
  5. 打开 ProductList.aspx.cs 的代码隐藏,并添加以下命名空间,如黄色突出显示:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using WingtipToys.Models;
    using System.Web.ModelBinding;
    using System.Web.Routing;
    
  6. GetProducts 代码隐藏 (ProductList.aspx.cs) 的 方法替换为以下代码:

    public IQueryable<Product> GetProducts(
        [QueryString("id")] int? categoryId,
        [RouteData] string categoryName)
    {
        var _db = new WingtipToys.Models.ProductContext();
        IQueryable<Product> query = _db.Products;
    
        if (categoryId.HasValue && categoryId > 0)
        {
            query = query.Where(p => p.CategoryID == categoryId);
        }
    
        if (!String.IsNullOrEmpty(categoryName))
        {
            query = query.Where(p =>
                String.Compare(p.Category.CategoryName,
                categoryName) == 0);
        }
        return query;
    }
    

为产品详细信息添加代码

现在,更新 ProductDetails.aspx 页的代码隐藏 (ProductDetails.aspx.cs) ,以使用路由数据。 请注意,新 GetProduct 方法还接受查询字符串值,因为用户具有使用较旧的非友好、非路由 URL 添加书签的链接。

  1. GetProduct 代码隐藏 (ProductDetails.aspx.cs) 的 方法替换为以下代码:

    public IQueryable<Product> GetProduct(
            [QueryString("ProductID")] int? productId,
            [RouteData] string productName)
    {
        var _db = new WingtipToys.Models.ProductContext();
        IQueryable<Product> query = _db.Products;
        if (productId.HasValue && productId > 0)
        {
            query = query.Where(p => p.ProductID == productId);
        }
        else if (!String.IsNullOrEmpty(productName))
        {
            query = query.Where(p =>
                  String.Compare(p.ProductName, productName) == 0);
        }
        else
        {
            query = null;
        }
        return query;
    }
    

运行应用程序

现在可以运行应用程序以查看更新的路由。

  1. F5 运行 Wingtip Toys 示例应用程序。
    浏览器将打开并显示 Default.aspx 页。
  2. 单击页面顶部的 “产品 ”链接。
    所有产品都显示在 ProductList.aspx 页面上。 浏览器将显示以下使用端口号) 的 URL (:
    https://localhost:44300/ProductList
  3. 接下来,单击页面顶部附近的 “汽车 ”类别链接。
    只有汽车显示在 ProductList.aspx 页面上。 浏览器将显示以下使用端口号) 的 URL (:
    https://localhost:44300/Category/Cars
  4. 单击包含页面上列出的第一辆汽车名称的链接, (“可转换汽车”) 显示产品详细信息。
    浏览器将显示以下使用端口号) 的 URL (:
    https://localhost:44300/Product/Convertible%20Car
  5. 接下来,在浏览器中使用端口号) 输入以下非路由 URL (:
    https://localhost:44300/ProductDetails.aspx?productID=2
    对于用户为链接添加书签的情况,代码仍可识别包含查询字符串的 URL。

摘要

在本教程中,你添加了类别和产品的路由。 你已了解如何将路由与使用模型绑定的数据控件集成。 在下一教程中,你将实现全局错误处理。

其他资源

ASP.NET 友好 URL
将具有成员身份、OAuth 和SQL 数据库的安全 ASP.NET Web Forms应用部署到Azure 应用服务
Microsoft Azure - 免费试用版