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 page
Trang 1CHAPTER 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
Trang 2Using the VirtualPathProvider Class
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);
}
Trang 3CHAPTER 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
Trang 4Using 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
Trang 5CHAPTER 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
Trang 6Basic 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”);
Trang 7CHAPTER 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 parameparame-ters 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 ever throw the GUID of a blog post entry in 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 opportuncapabil-ity 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
Trang 8Basic 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
Trang 9CHAPTER 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
Trang 10Advanced 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