Click here to Skip to main content
Click here to Skip to main content

Strongly typed page references in ASP.NET

By , 31 Jul 2007
 

Introduction

Constructing the URL to a page within your solution is typically a messy task. Wouldn't it be nice if you could just say something like:

Redirect(new PrintItemPage(item, itemFormat))

That, of course, will not work.

So, instead of some nice, clean, strongly typed solution like that, we see people embedding references like "~/Print/Printitem.aspx" all over their solution. And, if that page needs parameters, they may well use string concatenation to construct the URL, maybe something like:

string url = "~/Print/Printitem.aspx?id=" + item.Id + "&format=" + itemFormat;

The smarter developer will have realized that it's better to define the basic URL string in a single location in the solution, and will have a constant string for that:

// In the code behind for Printitem.aspx in the /Print directory
public const string baseUrl = "~/Print/Printitem.aspx?id={0}&format={1}";

But typically, the parameters for that page are still added in a weakly typed manner to the URL, something like:

string url = string.Format(Printitem.baseUrl, item1.id, item2.format)

Background

Code such as that shown in the introduction has several issues:

  1. References to ~/Print/Printitem.aspx are often scattered throughout the solution, making it hard to move a page to a different folder or to rename it.
  2. The parameters for the page are weakly typed, allowing run-time errors when someone accidentally links to that page, passing the ID for a Foo instead of an Item.
  3. It is hard to discover (using the IDE) what the various parameter combinations are that can be used when referring to a different page in the solution.

Using the code

The solution to these issues is to create a strongly-typed static method on each of your page classes that returns a fully formed URL for that page. If there is more than one way to call the page, then provide multiple such static methods on your page class.

/// <summary>
/// Construct a URL to self with an Item and an ItemFormat
/// </summary>
public static string UrlToSelf(Item item, Itemformat itemFormat)
{
    return string.Format("~/print/printitem.aspx?id={0}&format={1}", 
                         item.id, itemFormat.ToString());
}

/// <summary>
/// Construct a URL to self with just an Item (will use the default ItemFormat)
/// </summary>
public static string UrlToSelf(Item item)
{
    return UrlToSelf(item, ItemFormat.Default);
}

With these static methods in place, you can now discover (using the IDE) how to call the PrintItem page. Intellisense will show you that there are two calls you can make and that you must pass in an Item object.

Screenshot - Article.gif

It's now impossible for you to link or redirect over to the PrintItem page passing it anything but the object type it expects.

Points of interest

Duplicate code is always a bad idea: it leads to code bloat; it causes bugs to appear in some places, but not others; bugs 'fixed' mysteriously reappear in other places because the developer didn't go find all copies of that code when he or she fixed a bug that had been found in one of them.

"One-fact, one-place" is a great mantra not just for database developers, everyone should aim for it. If you are ever tempted to use "cut-and-paste coding" because it's 'quicker', just remember that 2x the code will produce 2x as many bugs and will be 2x as hard to maintain. It is always better to avoid duplication and to spend time creating a reusable method that puts "one fact" in "one place". Every sprint cycle should include time for this kind of refactoring to ensure that your solution stays clean and maintainable.

Related article

See also ... another article on how to do the same for Web User Controls.

History

  • July 2007, First version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

HightechRider
United States United States
Member
I have been writing code and managing teams of developers for more years than I care to remember.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 2memberPaulo Zemek15 Apr '09 - 2:29 
This does not discover the path automatically, you are needed to create static methods to each page... also, when you refactor, you need to go to the method and change it.
GeneralRe: Auto-discovery of pathmemberHightechRider20 Apr '09 - 11:25 
One of the key objectives of this technique is to ensure that there is only ONE place in your code where the path and all the parameter names are defined for any given page. As such refactoring is trivial and safe - when you change the location of an ASPX page, you have just ONE string to change; when you change a parameter, the code to encode it, write it, read and parse it are all sitting in the same .CS file.
 
Provided you stick with the principle: all page references MUST be generated ONLY by code sitting in static methods on the page in question your solution will be a lot more maintainable than the average ASP.NET solution with its scattered page-naming and parameter logic.
Questionforcing implementationmemberjimischacht28 Nov '07 - 5:06 
So, i want to use this technique. What i'd like to do in my design is to force all the pages on my site to implement this. I use a base class that extends Page for all my pages but you cant make a static member abstract in abstract classes or methods. can anyone else think of a way to do this?
 
"A common mistake that people make when trying to design something completely foolproof is to underestimate the ingenuity of complete fools. "
~Douglas Adams~
AnswerRe: forcing implementationmemberHightechRider28 Nov '07 - 9:09 

Unfortunately there is no way to force this contract on all pages.
 
Try a search on Google for 'C# static interface': you'll find plenty of people who want it and a few who can explain why it's not possible.
 
Another issue is that the signature of this method is going to be different for every page depending on the page's parameters so that's another reason why you can't enforce it.
 
That leaves you with 'developer rules' which you might phrase as:
 
(i) Never hard-code an URL, or the logic for constructing an URL into any page other than itself. [ENCAPSULATION]
 
(ii) There should be only ONE place in your entire solution where the URL to any given page is found and it should be on that page itself. [ONE-FACT-ONE-PLACE, ENCAPSULATION]
 
(iii) Never store a generated URL for longer than the lifetime of the current application deployment (i.e passing it to the client to use in an Ajax call-back is OK, but storing it in a database or some external system is not).
 

A search for ".aspx" should reveal no references outside these static methods.
GeneralIt's greatmemberLucianoCN9 Aug '07 - 19:58 
But I think microsoft guys have use this in petshop4.0, as :
 
private const string REDIRECT_URL = "~/Search.aspx?keywords={0}";
 
public static void SearchRedirect(string key)
{
HttpContext.Current.Response.Redirect(string.Format(REDIRECT_URL, InputText(key, 255)));
}
 
Am i right?
GeneralGlad to see my idea is being publicized.memberPeter Lanoie7 Aug '07 - 4:39 
This sounds strikingly familiar to an article I wrote 2 years ago.
 
Getting a page's URL in code-behind
 
Peter

GeneralBrilliant!memberMark II6 Aug '07 - 22:58 
Thanks for a great solution. I appreciate its simplicty.
 
The one thing I hate most about ASP.Net is the hard-coded strings all over the place - in urls, session items names, etc.. They run completely counter to the spirit of an OO development environment. If only MS had seen fit to include something like this as part of the framework.
 

GeneralNicememberHoward Richards6 Aug '07 - 21:45 
A good idea, thanks for the article.
 
As for me I had a slightly different problem: my objects are all in a separate DLL which of course has no access to page or ascx classes in the ASP.NET application, so I had to go down a different route.
 
When using ObjectDataSource putting links on the screen was best done using properties, so I would define a set of link properties, e.g.

class Customer
 
string Link
{
get { return LinkStatic(this.CustomerID); }
}
 
static LinkStatic(int CustomerID)
{
return string.Format("~/path/Customer.aspx?CustomerID={0}", CustomerID);
}

 
I don't really like this because UI logic is coded in my data layer, but it was the easiest way to achieve this.
 
'Howard

GeneralPoints of Interest, Yeah duhmemberRasgis6 Aug '07 - 19:18 
From the subject, you might think i'm not liking your advice, but I am. Although very obvious it's good to hear it every so often. With copying and pasting code, you always think it'll only be this one time, but then you need it somewhere else, and you have already copied and pasted it somewhere else and cant even remember where that is, so you copy and paste again, mind you this is the last time (ha ha). Great article *****
 
StanleyG
GeneralKeep up the good workmemberGreatPokerHands5 Aug '07 - 20:16 
A helpful article.
 
www.GreatPokerHands.com

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 1 Aug 2007
Article Copyright 2007 by HightechRider
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid