Creating Webforms with Friendly URLs

If there’s one stick the ASP.NET MVC crowd loves to rattle when comparing MVC and webforms, it’s the restful/friendly URL one. From the get go, ASP.NET MVC is designed to handle URLs that are detached from the page or resource to which they are tied.

The types of URLs that I’m talking about are the ones that look like this:

http://myserver.com/product/info/1

or

http://myserver.com/profile

These URLs not only look better than a typical webforms URL, but they are also by their very nature a little more secure too, because they don’t reveal the page or resource name that they refer to, and as a result the reduce the attack vector quite considerably. They also work better when trying to tune your site for SEO purposes because they are much more accessible by the search engine spiders that profile your site. Unfortunately, all that has (for a while, at least) been out of reach for the average webforms developer.

There have been hacks, of course (just as there is with every technology). Some folks found creative ways of using the rewrite module; others hacked their own IIS handler, and so on.

That’s now changed, however. Introduced in .NET4 (along with the ASP.NET ONE initiative) was the routing engine that’s used in ASP.NET MVC, and what’s more is that it is amazingly easy to use and set up.

Hang On, Though. Wasn’t This Introduced Way Back in 2009 with 3.5?

Well yes, kind of. The seeds that led to the latest version of it were, yes.

Back then, however, it was rather cumbersome and hard to use. You had to write an 'IRouteHandler' that took the virtual path to your page as a string. This handler had to get a copy of the HTTPHandler pointing to the page, and then do some mapping and extract the route values.

You also had to put a couple of hacks into the web-config file to make sure the correct modules where loaded, and, if you were targeting IIS6, you had a lot more work to do due to the extension-less nature of the URLs. It was all, to say the least, a bit messy and prone to error.

ASP.NET4, however, made things so much easier. Let’s try a little example to see just how easy.

Step 1: Create a New Webform

Fire up Visual Studio, and do the usual File->New Project manoeuvre.

Choose to create an “ASP.NET Web Forms Application”, or, if you’re using a newer run time, an “ASP.NET Web Application”. If you’re using the latter “ASP.NET Web Application”, you’ll get the new “ASP.NET One” dialog, which will look something like this:

Webform1
Figure 1: The New dialog box

If you do get this dialog, choose “Empty”, and tick the “Web Forms” box below. Don’t tick anything else. If you’ve picked the former “ASP.NET Web Forms Application”, the wizard will just go ahead and build you a fully ASP.NET webforms app.

That’s fine; you can use the pages in that if you wish, but I’ll be doing the rest of this post from the point of view of creating the web pages myself.

Step 2: Add Some Standard Webform Pages to Your New Project

Once the project has been created, right-click on the project in Solution Explorer, and add some new webforms. For simplicity, we’ll not be using master pages or anything like that here; we’ll just be adding plain straightforward ASPX pages.

Go ahead and create the following pages:

  • Default.aspx
  • Page2.aspx
  • Page3.aspx

Step 3: Get Funky with ASP.NET Routing

Defining routes for an ASP.NET page is done in much the same way as defining routes for an MVC app In your ‘Global.asax’ page, find or create your ‘Application_Start’ handler. Into this file, add a method to set your routes up, and make sure you call it from Application_Start. It should look something like this:

public class Global : System.Web.HttpApplication
   {
      protected void Application_Start(object sender, EventArgs e)
      {
         SetUpRoutes();
      }

      private void SetUpRoutes()
      {
      }

   }

You don’t have to put it in a separate method, but because we like to keep our code tidy and easy to maintain, it’s good practice.

Once you have this in place, you have a point that will always get called when your app starts up, so now we can define some routes. Routes are added by the RouteTable class and its methods that are now part of every standard webform page created in ASP.NET 4 now.

We want to add a route to get us back to the default home page, and one for each of the two separate pages we created. Add the following code into the SetUpRoutes method you created just a moment ago.

   RouteTable.Routes.MapPageRoute("page3", "Page3/{itemid}", "~/Page3.aspx");
   RouteTable.Routes.MapPageRoute("page2", "Page2/", "~/Page2.aspx");
   RouteTable.Routes.MapPageRoute("default", "", "~/Default.aspx");

The first route defines a page route called ‘/page3/xxx’ that points to ‘Page3.aspx’ and takes a non-optional parameter called ‘itemid’. (We’ll see in a moment how to retrieve this.) The second route defines a route as ‘/page2/’ that points to ‘Page2.aspx’ and takes no parameters. As with ASP.NET MVC, if you attempt to call page3 without the parameter, you’ll get a 404 file not found error.

If you want to make the parameter optional, you need to define your route with an extended syntax, as follows:

RouteTable.Routes.MapPageRoute("page3", "Page3/{itemid}", "~/Page3.aspx", true, new
RouteValueDictionary { { "itemid", "1" } });

You can see you have the first three parameters as normal; then, you have ‘true’ followed by a route value dictionary. The true value means check the route for user access. In other words, make sure that by using whatever membership service you have in place, the user is allowed to access this.

The last value is the one that allows you to set a default value for your route variables. (In this case, you’ll see I’ve set it to 1.) What this means now is that if I were to just call ‘/Page3/’ in the browser, it would call the route adding in a 1 for the missing parameter. The second route that’s defined simply just points ‘/Page2/’ at ‘Page2.aspx’with no parameters of any kind.

Finally, the last route has an empty string for the URL, and is essentially the “/” or home route of the entire site.

Why Do We Use an Empty String?

When defining route URLs, you cannot start them with a ~ or a / symbol; the / in-particular is already implied, so specifying an Empty string says “/” is the path this should match.

Once you’ve defined the routes, the last thing to do is to use them. In this example, add some content into Default.aspx.

What you add is entirely up to you, but you need add at least one asp:HyperLink control, so you can add a link to page2.aspx to set the URL in the hyperlink declaratively in the server tag in HTML, you use the ASP.NET <%$ %> tags to define it.

Add a hyperlink into your page code that looks like this:

&<asp:HyperLink runat="server" ID="theLink" NavigateUrl="&<%$RouteUrl:routename=page2 %>">Click here to go to the second page</asp:HyperLink>

Note that it’s all quite normal, except for the navigate URL attribute.

As well as access to the RouteTable to add routes, webforms now also has a method called ‘RouteUrl’ available for creating a friendly URL using the routing table. In this case, I’ve set this one declaratively, using ASP front-end HTML code, but I could just as easily have called RouteUrl in the code behind as follows:

theLink.NavigateUrl = GetRouteUrl("page2", null);

The ‘null’ is important. If you’re not passing any route dictionary parameters to the route, you still need to set this. Otherwise, ASP.NET will add a strange ‘length=x’ parameter to your URL. I’ve no idea what/why it does it, but it does. If you put null in, however, it prevents it happening.

If you want to pass a parameter (for example, to call page 3), you’d define the code behind call as follows:

theLink.NavigateUrl = GetRouteUrl("page3", new { itemid = 1});

making sure to substitute the correct parameter names where needed.

Moving on, let’s also add some content onto page2 and add a link back to the default page, and some links to page 3 to pass a category. It will look something like the following:

<asp:HyperLink runat="server"
               ID="theLink" NavigateUrl="<%$RouteUrl:routename=default %>">
               Click here to go back to default</asp:HyperLink>
<ul>
   <li><asp:HyperLink runat="server" ID="p3l1"
                      NavigateUrl="<%$RouteUrl:itemid=1,routename=page3 %>">
                      Page 3 - Category 1</asp:HyperLink></li>
   <li><asp:HyperLink runat="server" ID="p3l2"
                      NavigateUrl="<%$RouteUrl:itemid=2,routename=page3 %>">
                      Page 3 - Category 2</asp:HyperLink></li>
   <li><asp:HyperLink runat="server"
                      ID="p3l3" NavigateUrl="<%$RouteUrl:itemid=3,routename=page3 %>">
                      Page 3 - Category 3</asp:HyperLink></li>
   <li><asp:HyperLink runat="server"
                      ID="p3l4" NavigateUrl="<%$RouteUrl:itemid=4,routename=page3 %>">
                      Page 3 - Category 4</asp:HyperLink></li>
</ul>

The link back to default is the same as previous, but with the links to page 3, you’ll see we’ve also specified the name of our parameter and its value. When the link is rendered in the page for these, the page 3 links will display as

/page3/1

or

/page3/2

and so on.

This will allow you to pass values in for things like database lookups or page IDs, for instance.

If you then place an asp:Label into page3 and then add the following code behind in page 3 inside the ‘Page_Load’ event:

string category = RouteData.Values["itemid"].ToString();
lblThankYou.Text = "Thank you for choosing category " + category;

You’ll find that, when the links in page 2 are clicked, page3.aspx will load, with a nice restful URL, and the page will be able to grab the parameter.

And that’s pretty much it. That’s the basics of adding ASP.NET routing to an existing or new webforms project. Use RouteUrl/GetRouteUrl to generate your URLs using the names you gave to each route URL rule, and then grab any parameters you need using the RouteData.Values collection that is now available to every webform page request.

Now you too can have MVC-style URLs in your webform apps. If you have any ideas for any subjects you’d like to see covered in this column, please feel free to find me on Twitter (@Shawty_ds), or hunt me down on Linked-in where I help run the Lidnug .NET users group, and ask me. If I can write a post around your suggestion, I happily will.

Until next time, have fun with your URLs.

Shawty

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read