URL Routing
In this tutorial, you will modify the Wingtip Toys sample application to support URL routing. Routing enables your web application to use URLs that are friendly, easier to remember, and better supported by search engines. This tutorial builds on the previous tutorial “Checkout and Payment with PayPal” and is part of the Wingtip Toys tutorial series.
What you'll learn:
- How to register routes for an ASP.NET Web Forms application.
- How to add static routes to a web page.
- How to add dynamic routes to a web page.
- How to select data from a database to support dynamic routes.
ASP.NET Routing Overview
URL routing allows you to configure an application to accept request URLs that do not map to physical files. A request URL is simply the URL a user enters into their browser to find a page on your web site. You use routing to define URLs that are semantically meaningful to users and that can help with search-engine optimization (SEO).
Before implementing URL routing, the Wingtip Toys sample application linked to a product using the following URL:
http://localhost:1234/ProductDetails.aspx?productID=2
By implementing URL routing, the Wingtip Toys sample application will link to the same product using an easier to read URL:
http://localhost:1234/Product/Convertible%20Car
Routes
A route is a URL pattern that is mapped to a handler. The handler can be a physical file, such as an .aspx file in a Web Forms application. A handler can also be a class that processes the request. To define a route, you create an instance of the Route class by specifying the URL pattern, the handler, and optionally a name for the route.
You add the route to the application by adding the Route object to the static Routes property of the RouteTable class. The Routes property is a RouteCollection object that stores all the routes for the application.
URL Patterns
A URL pattern can contain literal values and variable placeholders (referred to as URL parameters). The literals and placeholders are located in segments of the URL which are delimited by the slash (/) character.
When a request to your web application is made, the URL is parsed into segments and placeholders, and the variable values are provided to the request handler. This process is similar to the way the data in a query string is parsed and passed to the request handler. In both cases, variable information is included in the URL and passed to the handler in the form of key-value pairs. For query strings, both the keys and the values are in the URL. For routes, the keys are the placeholder names defined in the URL pattern, and only the values are in the URL.
In a URL pattern, you define placeholders by enclosing them in braces ( { and } ). You can define more than one placeholder in a segment, but the placeholders must be separated by a literal value. For example, {language}-{country}/{action} is a valid route pattern. However, {language}{country}/{action} is not a valid pattern, because there is no literal value or delimiter between the placeholders. Therefore, routing cannot determine where to separate the value for the language placeholder from the value for the country placeholder.
Mapping and Registering Routes
Before you can include routes to pages of the Wingtip Toys sample application, you must register the routes when the application starts. To register the routes, you will modify the Application_Start event handler.
- In Solution Explorer of Visual Studio, find and open the Global.asax.cs file.
- Add the code highlighted in yellow to the Global.asax.cs file as follows:
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 WingtipToys; using System.Data.Entity; using WingtipToys.Models; using System.Web.Routing; namespace WingtipToys { public class Global : HttpApplication { void Application_Start(object sender, EventArgs e) { // Code that runs on application startup BundleConfig.RegisterBundles(BundleTable.Bundles); AuthConfig.RegisterOpenAuth(); Database.SetInitializer<ProductContext>(new ProductDatabaseInitializer()); // Add Administrator. if (!Roles.RoleExists("Administrator")) { Roles.CreateRole("Administrator"); } if (Membership.GetUser("Admin") == null) { Membership.CreateUser("Admin", "Pa$$word", "[email protected]"); Roles.AddUserToRole("Admin", "Administrator"); } // Add Routes. RegisterRoutes(RouteTable.Routes); } void RegisterRoutes(RouteCollection routes) { routes.MapPageRoute( "HomeRoute", "Home", "~/Default.aspx" ); routes.MapPageRoute( "AboutRoute", "About", "~/About.aspx" ); routes.MapPageRoute( "ContactRoute", "Contact", "~/Contact.aspx" ); routes.MapPageRoute( "ProductListRoute", "ProductList", "~/ProductList.aspx" ); routes.MapPageRoute( "ProductsByCategoryRoute", "ProductList/{categoryName}", "~/ProductList.aspx" ); routes.MapPageRoute( "ProductByNameRoute", "Product/{productName}", "~/ProductDetails.aspx" ); } void Application_End(object sender, EventArgs e) { // Code that runs on application shutdown } void Application_Error(object sender, EventArgs e) { // Code that runs when an unhandled error occurs } } }
When the Wingtip Toys sample application starts, it calls the Application_Start event handler. At the end of this event handler, RegisterRoutes method is called. The RegisterRoutes method adds each route by calling the MapPageRoute method of the RouteCollection object. The MapPageRoute method is used to register both static routes and dynamic routes. Both static and dynamic routes are defined using a route name, a route URL and a physical URL. A static route is shown as follows:
routes.MapPageRoute( "HomeRoute", "Home", "~/Default.aspx" );
In a static route, the first parameter ("HomeRoute") is the route name. It is used to call the route when it is needed. The second parameter ("Home") is the route URL. It is the friendly replacement URL that is shown as part of the URL. The third parameter ("~/Default.aspx") is the actual path to the content that is displayed.
Using the HomeRoute, the Home link will navigate to the following URL:
http://localhost:1234/HomeA dynamic route is similar to a static route. However, the second parameter that defines the friendly replacement URL can be dynamic based on code. You use dynamic routes when you are populating a data control with links that are generated based on the data. A dynamic route is shown as follows:
routes.MapPageRoute( "ProductsByCategoryRoute", "ProductList/{categoryName}", "~/ProductList.aspx" );
The second parameter of the dynamic route includes a dynamic value specified by braces ({ }). In this case, the categoryName is a variable that will be used to determine the proper routing path.
Retrieving and Using Route Data
As mentioned above, both static and dynamic routes can be defined. The code that you added to the Application_Start event handler in the Global.asax.cs file loads both static and dynamic routes.
Setting Static Routes
You can use the GetRouteUrl method to render a route. For static routes, you will pass to the GetRouteUrl method the route name as the first parameter and a null placeholder as the second parameter. No dynamic values are passed.
Setting Static Routes
A static route doesn’t implement any dynamically generated data. You don’t use static routes with a data control. Instead, you use the GetRouteUrl method to retrieve the static route that you registered in the Global.asax.cs file.
- In Solution Explorer, find and open the Site.Master page.
- Update the nav element of the Site.Master page so the markup appears as follows:
<nav> <ul id="menu"> <li> <span id="adminLink" runat="server" visible="false"> <a href="/Admin/AdminPage.aspx">Admin</a> </span> </li> <li><a href="<%: GetRouteUrl("HomeRoute", null) %>">Home</a></li> <li><a href="<%: GetRouteUrl("AboutRoute", null) %>">About</a></li> <li><a href="<%: GetRouteUrl("ContactRoute", null) %>">Contact</a></li> <li><a href="<%: GetRouteUrl("ProductListRoute", null) %>">Products</a></li> </ul> </nav>
As mentioned, you pass the name of the route when you call the GetRouteUrl method. The GetRouteUrl method uses the name of the route to look up the route details that have been registered in the Global.asax.cs file. The returned values are then added to each link, so that routing paths appear as follows:
<li><a href="/Home">Home</a></li> <li><a href="/About">About</a></li> <li><a href="/Contact">Contact</a></li> <li><a href="/ProductList">Products</a></li>
Setting Dynamic Routes
Dynamic routes require you to add additional code. In this tutorial, you will use model binding to retrieve a RouteValueDictionary object that is used when generating the routes using data from a data control. The RouteValueDictionary object will contain a list of product names that belong to a specific category of products. A link is created for each product based on the data and route.
Enable Routes for Categories and Products
Next, you'll update the application to use the ProductsByCategoryRoute to determine the correct route to include for each product category link. You'll also update the ProductList.aspx page to include a routed link for each product. The links will be displayed as they were before the change, however the links will now use URL routing.
- In Solution Explorer, open the Site.Master page if it is not already open.
- Update the ListView control named “categoryList” with the changes highlighted in yellow, so the markup appears as follows:
<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>
- In Solution Explorer, open the ProductList.aspx page.
- Update the ItemTemplate element of the ProductList.aspx page with the updates highlighted in yellow, so the markup appears as follows:
<ItemTemplate> <td id="Td2" runat="server"> <table> <tr> <td> </td> <td> <a href="<%#: GetRouteUrl("ProductByNameRoute", new {productName = Item.ProductName}) %>"> <image src='/Catalog/Images/Thumbs/<%#:Item.ImagePath%>' width="100" height="75" border="1"/> </a> </td> <td> <a href="<%#: GetRouteUrl("ProductByNameRoute", new {productName = Item.ProductName}) %>"> <%#:Item.ProductName%> </a> <br /> <span class="ProductPrice"> <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> </table> </td> </ItemTemplate>
- Replace the GetProducts method of the code-behind (ProductList.aspx.cs) with the following code:
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; }
Add Code for Product Details
Now, update the code-behind (ProductDetails.aspx.cs) for the ProductDetails.aspx page to use route data. Notice that the new GetProduct method also accepts a query string value for the case where the user has a link bookmarked that uses the older non-friendly, non-routed URL.
- Replace the GetProduct method of the code-behind (ProductDetails.aspx.cs) with the following code:
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; }
Running the Application
You can run the application now to see the updated routes.
- Press CTRL+F5 to run the Wingtip Toys sample application.
The browser opens and shows the Default.aspx page. - Click the Products link at the top of the page.
All products are displayed on the ProductList.aspx page. The following URL (using your port number) is displayed for the browser:
http://localhost:1234/ProductList - Next, click the Cars category link near the top of the page.
Only cars are displayed on the ProductList.aspx page. The following URL (using your port number) is displayed for the browser:
http://localhost:1234/ProductList/Cars - Click the link containing the name of the first car listed on the page (“Convertible Car”) to display the product details.
The following URL (using your port number) is displayed for the browser:
http://localhost:1234/Product/Convertible%20Car - Next, enter the following non-routed URL (using your port number) into the browser:
http://localhost:1234/ProductDetails.aspx?productID=2
The code still recognizes a URL that includes a query string, for the case where a user has a link bookmarked.
Summary
In this tutorial, you have added static routes and dynamic routes for categories and products. You have learned how dynamic routes can be integrated with data controls that use model binding. In the next tutorial, you will implement global error handling.
Comments (0) RSS Feed