Web Navigation Strategies: Creating Contracts for Your Web Pages

Advisor Media

Problem:

You and Joe are working on a sizable web application. Nasty-Coder Joe, who is on another team, creates a new page that requires 2 session variables and a query string parameter in order for his page to work properly. You now need to pass data to this page to finish your last feature before you leave for vacation in one hour. Without reading Nasty-Coder Joe's code to try and figure out what the necessary session variables and QueryString paramenters are, how do you use his page?

Introduction:

Controlling page flow on the web has a number of distinct problems. Since the web is literally a "web" often the entry points to any one page are numerous. Many developers have come up with solutions to addressing the problem of page-flow in a web application, but the solutions sometimes end up becoming a thick mire of XML structures that try to be predictive in page-flow rather than facilitate the choose-your-own-adventures style of navigation often found in a web app.

Web Navigator is a technique, or pattern or approach that I came up with when I was faced with the same problem as stated at the beginning of this show. I was working alongside a developer who was creating pages that I needed to use, but I had no idea how to pass the correct information to the page.

What I came up with I call "WebNavigator".

Yes, I know I was wearing my terribly-original-idea-hat that day...

Web Navigatior is:

What is its Structure?

The syntax is based around a fluent interface. For example if you wanted to navigate to the homepage of the website, you would use:

WebNavigator.GoTo(WebNavigator.URLFor.Home());

Important points:

  1. Redirects are not done autmatically. The destination methods (ex: WebNavigator.URLFor.Home()) will always return strings. This is so you can still use the Web Navigator class to build URLs for you without requiring a post back to build the URL.
  2. Redirection is done only when necessary. WebNavigator.GoTo() handles the redirection. You can overload the method to support Server.Transfers and instructions to use SSL
  3. Composition helps the readability of the navigational structure of your code. For example, the following should give you a very good idea of where the user will end up: WebNavigator.GoTo(WebNavigator.URLFor.Membership.Login());
  4. Often pages don't require data, but they do change locations. If you have the access to a page wrapped up in WebNavigator.URLFor.Login() and it's real URL is http://domain.com/login.aspx, but used to be http://domain.com/login/default.aspx your website will not experience any broken links. You will change the mapping to the new location in one place and the rest of the site will reflect this change.

Examples of Encapsulation

When using pages throughout the website, some pages will require simple QueryString values and other pages will need session variables set and perhaps other requirements. Web Navigator will encapsulate the requirements of a page. When you are a developer consuming the page all you care about is what the destination method contracts in it's method signature.

For example, the following listing will show you how to pass a user's email address to the login screen.

string userName;

// set userName to something... perhaps from the database

WebNavigator.GoTo(WebNavigator.URLFor.Membership.Login(userName));

Using QueryString Values

The most common use of a destination method is to encapsulate the use of a QueryString parameter. The following example will show you how to hide the QueryString requirements.

public class Membership
{
    public string Login(string userName)
    {
        return string.Format("~/login/default.aspx?userName={0}",userName);
    }
}

Using Session Variables and Other Resources

Sometimes you do not want to pass an item the QueryString, or a page requires a complex-type before you can use it. (Think of a page requiring an object collection in memory). In these types of cases you could pass the data around in a session variable. The following will show you how to use session variables.

public class Reports
{
    public string SalesReport(OrderCollection orders)
    {
        System.Web.HttpContext.Current.Session.Add("SalesReport",orders);
        
        return "~/reports/sales.aspx";
    }
}

Using this method will set aside anything you need into session and then return the destination URL for you to use whenever necessary. The fun doesn't have to stop at session variables. You can use the same technique for application variables, context items, etc.

The point is that what is inside the implementaton does (setup QueryString vales, add Session variables, etc..) is completely hidden from the developer who is using the page. The method signature of the URLFor method will establish a contract for any required information for the page.

Contract Changes

If the contract to a page changes, you will encounter compilation errors. If the page in the past did not require any external data, but then was changed to needing a piece of data, the updated destination method signature will cause the application to encounter compilation errors where ever the page is used. This will ensure the page is always getting what it needs.

For instance if your content editor page previously did not require a content ID the method might look something like this:

WebNavigator.URLFor.ContentEditor();
.

Now you have decided you want to pass in the content ID so the call would look like this:

int contentID; 

// get contentID from somewhere

WebNavigator.URLFor.ContentEditor(contentID);

Handling SSL

You can implement it in different ways. I have done it as follows in the past:
WebNavigator.GoTo(WebNavigator.Secure(WebNavigator.URLFor.Login()));

Conculsion

Web Navigator is a pattern that you can use to help manage navigation throughout your websites. The strengths of this technique are:

  1. Encapsulation of page location
  2. Public contract of page requirements
  3. Single place of change for URLs (avoiding broken links)
  4. Manage SSL "auto-magically"
  5. Makes code easy to read and maintain
kick it on DotNetKicks.com
comments powered by Disqus