ptg 1134 CHAPTER 24 Advanced Navigation Typically, you override the FileExists() and GetFile() methods to retrieve a file from your data store. If you want to represent directories, you also need to override the DirectoryExists() and GetDirectory() methods. Several of these methods are related to caching. The VirtualPathProvider needs to know when a file has been modified so that it can retrieve the new version of the file and compile it. By default, the ASP.NET Framework uses a file dependency to determine when a file has been modified on the hard drive. However, in this situation a SqlCacheDependency is used because the files will be stored in a database. The VirtualPathProvider also includes a useful property: . Previous—Returns the previously registered VirtualPathProvider. The Previous property enables you to use the default VirtualPathProvider. For example, if you want to store some files in the file system and other files in the database, you can use the Previous property to avoid rewriting all the logic for working with files in the file system. The GetFile() method returns an instance of the VirtualFile class. When using the VirtualPathProvider, you must create a new class that inherits from the VirtualFile class. This class contains the following properties: . IsDirectory—Always returns False. . Name—Returns the name of the file. . VirtualPath—Returns the virtual path of the file. The VirtualFile class also contains the following method: . Open()—Returns the contents of the file. Typically, when creating a class that inherits from the VirtualFile class, you override the Open() method. For example, we override this method to get the contents of a file from a database table in the code sample built in this section. The GetDirectory() method returns an instance of the VirtualDirectory class. This class contains the following properties: . Children—Returns all the files and directories that are children of the current directory. . Directories—Returns all the directories that are children of the current directory. . Files—Returns all the files that are children of the current directory. . IsDirectory—Always returns True. . Name—Returns the name of the directory. . VirtualPath—Returns the virtual path of the directory. From the Library of Wow! eBook ptg 1135 Using the VirtualPathProvider Class 24 There is another class in the ASP.NET Framework that you want to use when working with the VirtualPathProvider class. The VirtualPathUtility class contains several useful methods for working with virtual paths: . AppendTrailingSlash()—Returns a path with at most one forward slash appended to the end of the path. . Combine()—Returns the combination of two virtual paths. . GetDirectory()—Returns the directory portion of a path. . GetExtension()—Returns the file extension of a path. . GetFileName()—Returns the filename from a path. . IsAbsolute()—Returns True when a path starts with a forward slash. . IsAppRelative()—Returns True when a path starts with a tilde (~). . MakeRelative()—Returns a relative path from an application-relative path. . RemoveTrailingSlash()—Removes trailing slash from the end of a path. . ToAbsolute()—Returns a path that starts with a forward slash. . ToAppRelative()—Returns a path that starts with a tilde (~). By taking advantage of the VirtualPathUtility class, you can avoid doing a lot of tedious string parsing on paths. Registering a VirtualPathProvider Class Before you can use an instance of the VirtualPathProvider class, you must register it for your application. You can register a VirtualPathProvider instance with the HostingEnvironment.RegisterVirtualPathProvider() method. You need to register the VirtualPathProvider when an application first initializes. You can do this by creating a shared method named AppInitialize() and adding the method to any class contained in the App_Code folder. The AppInitialize() method is automati- cally called by the ASP.NET Framework when an application starts. For example, the following AppInitialize method registers a VirtualPathProvider named MyVirtualPathProvider: public static void AppInitialize() { MyVirtualPathProvider myProvider = new MyVirtualPathProvider(); HostingEnvironment.RegisterVirtualPathProvider(myProvider); } From the Library of Wow! eBook ptg 1136 CHAPTER 24 Advanced Navigation Summary This chapter explored several advanced topics related to website navigation. In the first two sections, you learned how to map URLs from one path to another. In the first section, you learned how to configure remappings in the Web configuration file. In the second section, you learned how to build a custom HTTP module, which enables you to use wild- card matches when remapping a URL. In the final section of this chapter, you learned how to abstract pages in your application from the file system by using the VirtualPathProvider class. The techniques described in this chapter remain in this book mostly for backward compatibility with previous versions of ASP.NET. They still work in ASP.NET 4, but you might want to consider using the new Routing Engine if your goal is to provide powerful, flexible URL schemes that still invoke underlying ASPx pages. If you are simply pointing one location to another, or providing a URL scheme on top of nonexecutable content files, this chapter still applies to your situation. From the Library of Wow! eBook ptg CHAPTER 25 Using the ASP.NET URL Routing Engine IN THIS CHAPTER . Introduction to URL Routing . Basic URL Routing Scenarios . Advanced URL Routing . Summary One of the many new features introduced in ASP.NET 4 is the concept of URL routing. This chapter provides you with an introduction to URL routing and why it should concern you as an ASP.NET Web Forms developer, then continues with examples of various scenarios in which URL routing truly shines, and finishes with coverage of a few advanced usages of URL routing. When you finish with this chapter, you should have a firm grasp on the technology that makes URL routing work as well as when you might (or might not) want to utilize it in your own applications. Introduction to URL Routing URL routing, in general terms, is a system that enables the developer to build in dynamism, flexibility, and (hopefully) readability to the URLs that access the various parts of a web application. We’ve all seen URLs like the following: http://www.myapplication.com/server/client/apps/app2/ userdata.aspx?mode=1&system=22 &user=abc8341290c3120c1&sessionid= 2312cxjsfk3xj123&action=3123x31pb &jibberish=continuing&urlcomplexity=needless And we all know how ugly they are. Not only are they difficult to cut and paste, but they’re also not very memo- rable or not clean, and users never know whether they can feel free to copy them, Digg them, share them on Facebook, or whatever. The reason for this is that, to an From the Library of Wow! eBook ptg 1138 CHAPTER 25 Using the ASP.NET URL Routing Engine end user, a complicated-looking URL is an unusable or unsharable URL. They assume that there’s so much cruft jammed into the URL that it couldn’t possibly be portable. URL routing enables us to simplify those URLs, up to and including giving us the ability to build RESTful style URLs and even hide the .aspx extension entirely. We no longer even need a 1:1 mapping between the URL and physical page on disk. The first and foremost reason why URL routing should matter to you is that simple URLs make for calm users. The simpler your URL, the simpler people are going to assume your site is to use. Sure, this is often a bad assumption on the user’s part, but if you’re given an opportunity to make your users feel more welcome, shouldn’t you take it? Secondly, simple and easy URLs make for better Search Engine Optimization (SEO). An ASPx page that serves up a different product page when the query string varies by a productid parameter might look something like this: http://my.store.com/product_detail.aspx?productid=12 This is certainly more user-friendly than the previous URL, but is it search-engine friendly? Can a search engine crawler tell that more products are available on this same filename? There are all kinds of tricks you can do, including publishing a page that produces a master list of links to all products, but even those links aren’t the best links. What if each product detail page URL could include the short name of the product and the category to which it belonged? In this case, the URL carries information that might help make that URL more discoverable via search engines: http://my.store.com/products/dvdplayers/toshiba/sd1600 This URL, although simple, has packed a truckload of information into it that can not only be used by end users (they immediately know they’re looking at the URL for a Toshiba DVD player, model number SD-1600) but also provides search engine crawlers with a lot of context. Both human and web crawler alike can assume that if they use the /products/dvdplayers URL they will get all DVD players, and the /products/dvdplayers/toshiba URL should provide all Toshiba DVD players. Finally, dynamically routing between URL and physical page gives you the flexibility to use URL parameters to do things that might otherwise be cumbersome, difficult, or even impossible without routing. For example, take the following two URLs: http://my.app.com/blog/2010/08/12 and http://my.app.com/products/tags/discount/new/black The first one enables you to supply a date directly on the URL, but you don’t need to worry about figuring out what the query string parameter names are for the compo- nents—just put the date in the URL with slashes. This reduces a lot of complexity for the underlying blog page and for end users. From the Library of Wow! eBook ptg 1139 Basic URL Routing Scenarios The second URL enables you to supply an infinite list of tags directly on the URL. This enables power users to start adding tag after tag to the end of a URL to further limit their list of products—functionality available to users without the developer even having to provide a GUI for it. Basic URL Routing Scenarios Now that you’ve had an introduction to what URL routing is and why you might want to use it, let’s take a look at some of the basics of how to use URL routing in your Web Forms application. The ASP.NET MVC Framework also makes use of the URL routing engine, but it does so in a slightly different way. The MVC Framework uses the routing engine to map routes to controllers and actions with parameters whereas ASP.NET Web Forms applica- tions use the routing engine to map routes to physical files on disk and supply parameters to those pages. Mapping Basic URLs The first thing that needs to be done to use the URL routing engine is to register your route maps. A route map is just a mapping from a route expression to a physical file on disk (or a controller/action combination in the case of ASP.NET MVC Framework). These routes are registered at application startup time and the registration is typically wrapped in its own method, as shown in the following code snippet taken from a Global.asax.cs file: void Application_Start(object sender, EventArgs e) { // Code that runs on application startup InitializeRoutes(RouteTable.Routes); } private void InitializeRoutes(RouteCollection routes) { // perform route registration routes.MapPageRoute( ); } The simplest and most basic type of route is one in which you map a static route directly to an ASPx page without any parameters. You typically see this when the target page takes no parameters and has fairly simple functionality such as a login, logout, or about page. The following code shows how to use the MapPageRoute method of the RouteCollection class to add basic route mappings: routes.MapPageRoute(““, “about”, “~/About.aspx”); // anonymous route routes.MapPageRoute(“login”, “login”, “~/Login.aspx”); // named route (login) routes.MapPageRoute(““, “logout”, “~/Logoff.aspx”); 25 From the Library of Wow! eBook ptg 1140 CHAPTER 25 Using the ASP.NET URL Routing Engine The effect of these simple route mappings is that users can now use the URL http://server/about, and they will be given the content from the About.aspx page. The URL in the browser appears as “/about”. If that page were to post back to itself as many ASP.NET Web Forms pages do, the post back would be sent to the “/about” URL, but the About.aspx page would still be invoked. NOTE No matter what you have in a route rule, if the URL requested by the user corresponds to a physical file on disk, the routing rule will be ignored. This means any constraints and defaults defined by that rule will also be ignored. As a matter of practice (and good style), you should avoid writing route rules and contain the “.aspx” file extension to avoid accidental conflicts between physical files and routes. Mapping URLs with Parameters Static route mappings come in handy, but any application that has any kind of dynamic nature or is driven by an underlying data source is, at some point, going to need parame- ters passed on the query string. Mapping parameters within a route expression and making those parameters available to the target page is actually quite simple. Thankfully we don’t (yet) need to worry about regular expressions! Let’s assume that you’re creating a web application that has blogging functionality. Rather than force users to create (or look at) some horribly complicated URL syntax that might even include the GUID of the blog post, you can use URL routing to simplify that URL syntax. NOTE You may be thinking that no one would ev er throw t he GUID of a blog post entr y i n the blog URL, but we have actually seen it multiple times. Never underestimate the capabil- ity of the Internet to breed bad user experience, and take every opportunity you can to rid the Internet of such! A common convention used by many blogging platforms is to embed a date directly in the URL, enabling end users, GUIs, and crawlers to easily select the content they want. To do this, we need to direct traffic to URLs like this: http://my.app.com/blog/2010/05/12 to our blog rendering page (Blog.aspx). We’ve seen how to send static (never changing) URLs to individual ASPx pages, but how do we send dynamic URLs to those pages? To do this without the routing engine, we’d have to create a low-level HttpHandler to inspect the URLs, parse them, convert them into query string parameters, and then finally load up the appropriate page. Thankfully it’s much easier to just use the following code in our InitializeRoutes method: From the Library of Wow! eBook ptg 1141 Basic URL Routing Scenarios // URL pattern: blog/2010/05/12 routes to blog.aspx routes.MapPageRoute(“blog”, “blog/{year}/{month}/{day}”, “~/Blog.aspx”); The words inside the curly braces are now named parameters within the routing engine and, when captured, will be made available to the Blog.aspx page. At this point you might be tempted to try and access these parameters as part of the Request.QueryString object. Don’t! Parameters passed through the URL routing engine are made available inside the Page.RouteData property and not as part of the query string, as shown in the following code: protected void Page_Load(object sender, EventArgs e) { string year = RouteData.Values[“year”] as string; string month = RouteData.Values[“month”] as string; string day = RouteData.Values[“day”] as string; // Perform blog processing based on date parameter } As you see as you progress through the chapter, this is just the beginning of what can be done with route expressions. NOTE Parameters captured by the routing engine that show up in the RouteData dictionary are always stored as strings when used with ASP.NET Web Forms, even if you have con- strained their data types via regular expressions (shown later in the chapter). As a result, it is up to you to convert them to their final destination types after getting them from the RouteData dictionary. ASP.NET MVC does a little extra work on your behalf to convert parameters into the data types the controllers expect. Mapping URLs with Multiple Segments The technique in the preceding section is great if you know ahead of time exactly which parameters are going to be in the URL and how many of them there are going to be. But what do you do if you don’t know how many parameters you’re going to have, or you want people to pass a list of data on the URL without using cumbersome query string syntax? For example, let’s say that you have an application that exposes data that can have a deeply nested hierarchy and you want users to drill into that hierarchy directly from the URL. Thankfully, the URL routing engine gives us the ability to map a list of parameters. An example of such a URL might enable the user to drill down to a location geographi- cally, such as http://my.app.com/location/Earth/USA/NY/NYC/TimesSquare 25 From the Library of Wow! eBook ptg 1142 CHAPTER 25 Using the ASP.NET URL Routing Engine or as we’ll see in the next code sample, we can supply a list of tags to filter data in a flat hierarchy: http://my.app.com/tags/free/ammo/guns (should return everything tagged with free, ammo, and guns, though we might be a little scared to see those results). Multiple parameters can be supplied to a mapping by putting an asterisk in front of the named parameter: // URL pattern with variable segments routes.MapPageRoute(“products-by-tag”, “products/tags/{*tagnames}”, “~/ProductsByTag.aspx”); And then in the ProductsByTag.aspx.cs we can obtain the list of values passed on the URL as a slash-delimited list: string tagNames = RouteData.Values[“tagnames”] as string; string[] tagList = tagNames.Split(‘/’); Response.Write(string.Format( “You wanted products that have the following tags: {0}”, string.Join(“ : “, tagList))); Linking to Other Pages with Routes So far we’ve looked at a bunch of ways we can get a user from a URL to a page, but what about going the other way? When the user is on an ASP.NET Web Form, how do we create links to other pages in a way that respects the URL routing scheme without giving each page “magic” knowledge of how the URLs are formatted? Basically what we want is to keep all the URL routing logic inside the single initialization method at application startup. We never want individual Web Forms to be creating links with hard-coded strings or string concatenation because those pages would cease to work properly if the URL route maps changed. Ideally, we want to create URLs dynamically by supplying parameters and, optionally, the name of the route map. We can do this either declaratively in the ASPx markup or we can build the URL programmatically in the code behind. First, let’s take a look at how we can link to another page using the asp:HyperLink server control tag: <asp:HyperLink ID=”testLink” runat=”server” NavigateUrl= “<%$ RouteUrl:RouteName=products-by-tag,tagnames=new/discounted %>”>New and DiscountedProducts </asp:HyperLink> From the Library of Wow! eBook ptg 1143 Advanced URL Routing This markup relies on the RouteUrlExpressionBuilder class to perform the appropriate route map lookup, apply the supplied parameters to the expression, and return a virtual path that respects the route. In the preceding code, the link returned will be for the /prod- ucts/tags/new/discounted relative path. We supplied the tagnames parameter directly; we did not manually construct the URL fragments. This insulates the Web Form from changes to the URL mapping. If one day we get tired of using “products/tags” and decide to change the mapping to “products/list/bytags” as the prefix, we won’t have to change any of this code—it will just work. If we want to generate a route-aware URL programmatically, we can do so using the following code (the codeGeneratedLink object is just a hyperlink control): RouteValueDictionary parameters = new RouteValueDictionary() { { “tagnames”, “new/discounted” } }; VirtualPathData vpd = RouteTable.Routes.GetVirtualPath( null, “products-by-tag”, parameters); codeGeneratedLink.NavigateUrl = vpd.VirtualPath; NOTE Although these techniques make working with the routing engine seamless, they also have an added benefit: the removal of hard-coded URLs. Hard-coded URLs are the bane of many a website, and by the very act of adopting the use of URL routing in your web- site, your team will be forced to work without hard-coded URLs. A great functional test would be to add a random word to all the route paths to see if your site continues to function. As we all know, every time you remove a magic string from your code, an angel gets its wings. Advanced URL Routing So far you’ve seen examples of how to use URL routing to deal with static routes, routes with known parameters, and even routes with lists of parameters of unknown sizes. In this next section we examine several other advanced scenarios that truly show off the power and flexibility of the ASP.NET URL routing system. Using Routes with Default Parameters Another powerful feature of the URL routing system is the capability to supply some meaningful defaults for route parameters. For example, you might have a product category page that displays products within that category. If the user doesn’t specify a category, you might want to pick a default one. 25 From the Library of Wow! eBook . RouteUrlExpressionBuilder class to perform the appropriate route map lookup, apply the supplied parameters to the expression, and return a virtual path that respects the route. In the preceding. and actions with parameters whereas ASP. NET Web Forms applica- tions use the routing engine to map routes to physical files on disk and supply parameters to those pages. Mapping Basic URLs The. results). Multiple parameters can be supplied to a mapping by putting an asterisk in front of the named parameter: // URL pattern with variable segments routes.MapPageRoute(“products-by-tag”, “products/tags/{*tagnames}”,