Saturday, June 25, 2005

A Page.GetUrl() Method

A recent post on the Wrox/Wiley programmer to programmer forum asked the question:

"How do I get the URL to a page without hardcoding the page URL?"


While it would be a very nice feature to have in .NET it seems to make sense that there is no method that will generate a URL for you to a page even though we have strongly typed code-behind classes that often times drive the page. Consider that the pages are just ASPX files that can be named anything and can live anywhere in your application structure. They can be derived from any page-derived class in the accessible namespaces (the framework supplied System.Web.UI.Page class, or any class that you create in your application assemblies that was derived from the framework's web page class). This provides us with very flexible design and application structure but as a result we don't have a reliable way to say "give me the URL for X".

A possible solution to this that solves two problems at once is to create a shared/static "GetUrl" method on the pages. This method provides the root-relative URL to the page that owns the code-behind class as well as specifying required parameters so you don't have to guess at them or encounter problems later on.

Let's take an example of a class that shows the attributes for some item. This class lives at the root of our application:

   ourApplication/ItemAttributes.aspx


Here's the code-behind class:

Public Class ItemAttributes
Inherits System.Web.UI.Page
...
Public Shared Function GetUrl(itemID As Integer, page As Page) As String
Return page.ResolveUrl("~/ItemAttributes.aspx?itemID=" & itemID)
End Function
End Class


The GetUrl() method expects a couple of arguments: first an itemID because we need a reference to some item so we know what item's attributes to show; second, an instance of a Page class. We need the instance of the page class so we can access the ResolveUrl() method. Now, any other pages in the application can create links to this page by calling the public shared method:

   ItemAttributes.GetUrl(12, Me)
'returns "/ourApplication/ItemAttributes.aspx?itemID=12"


A benefit of this technique is that we get a more robust application that is not quite as suceptible to growing pains. Let's say that we move this page because our application is getting bigger and the pages are getting cluttered. We'll move the page to a directory of common pages:

   ourApplication/common/ItemAttributes.aspx


We can just modify the GetUrl method and all the pages that consume this page link will follow along without a glitch:

   Return page.ResolveUrl("~/common/ItemAttributes.aspx?itemID=" & itemID)


Now let's imagine that we have to add another querystring value to the page because we now need to get information about the category that this item is in. When we modify our page to look for the querystring value all the page requests will start failing. We don't know all the places that this page is called in the application so we will have to do a pretty good job testing the application to find the now-broken references. However, there is salvation! All we have to do is change the method signature to expect the new argument:

   Public Shared Function GetUrl(itemID As Integer, categoryID As Integer, page As Page) As String


Now when we try to the build the application we'll get compile errors everywhere that this method is called. We can go and repair all those broken calls. Of course, depending on how you build your pages, you could add an overloaded method that still accepts the original set of arguments and have it call the new method with a default value so you don't have to repair broken calls. This is a great approach if you can build the page to function without the additional information.

By using this technique you get several advantages:
1. Flexibility to move pages
2. Compile-time debugging (reduces testing and potential customer discoveries of overlooked bugs)
3. Multi-developer teams don't have to waste precious time researching what the page expects for a querystring because the calls have a strongly typed and descriptive signature.

No comments: