Click here to Skip to main content
15,867,594 members
Articles / Web Development / ASP.NET
Article

UrlBuilder Part II - Encoded QueryStrings

Rate me:
Please Sign up or sign in to vote.
4.65/5 (12 votes)
29 Sep 20057 min read 115.3K   602   65   35
Taking UrlBuilder to the next level.

Introduction

This article follows on from a previous article I wrote called "A useful UrlBuilder class", also posted here on CodeProject. If you haven’t already read that article, you should probably do so now. This article will follow on from where that one left off.

Since I wrote the original article and realized a lot of people have a definite need for a class like this, I’ve been thinking about ways to improve and extend the UrlBuilder class. The original class provides a simple and effective way of dealing with URIs and I think it’s enough to meet most developers' needs. In this article, I’ll go into an explanation of the inner workings of the UrlBuilder class as well as outline the new encoding extensions which make URIs more robust and secure.

Before getting into the code internals, it’s probably useful to provide a brief explanation of what this new version aims to do. For a long time, developers have had to rely on using QueryString parameters to maintain state and pass information between web pages. This method of information exchange has often been error prone and susceptible to user tampering. While having control over QueryString parameters can sometimes be useful to power users, it more often than not serves only to create more headaches for the developer, who has to code in additional checks to make sure the user hasn’t been naughty. This version of UrlBuilder aims to address these problems by providing a simple method of encoding and decoding the QueryString between round trips to the server. In this way, the user never gets to see anything other than an encoded string of characters after the "?". A major advantage of encoding is that if even one character is changed, the QueryString won’t be valid when attempting to decode it. The QueryString can be encoded in any way the developer chooses by simply implementing an interface called IQueryStringEncoder, defined inside UrlBuilder. I hope that was enough to wet your appetite sufficiently to carry on reading. From here, we’ll get right into the code.

UrlBuilder internals

As with the previous version of UrlBuilder, this version implements all the constructor overloads of the base UriBuilder class, but it also adds a few extras to deal with the encoding extensions mentioned earlier. Wherever a constructor takes any QueryString information, it has an overload to also take an object of type IQueryStringEncoder. The interface is defined as follows:

C#
public interface IQueryStringEncoder {
  string Encode(string s);
  string Decode(string s);
  string Prefix {
    get;
  }
}

The first two method members are self explanatory so I won’t say anything more about those. The Prefix member however deserves some explanation. When a new UrlBuilder is instantiated, the textual QueryString is passed to a private method which parses the string and populates the internal QueryStringDictionary. The problem is, how can we tell at this point whether the QueryString being passed is already encoded? We can’t really, or not easily anyway, so for this reason, every URI read or generated with encoding must have a prefix which UrlBuilder can internally check for. If the prefix is found, the QueryString is decoded before being parsed.

For the purpose of this article, I’ve included two implementations of IQueryStringEncoder which we’ll get onto shortly.

Apart from the constructors already present in the base UriBulder class, it is useful to define an additional one which takes a System.Web.UI.Page object.

C#
public UrlBuilder(System.Web.UI.Page page) : 
                              base(page.Request.Url) { 
  Initialise();
}

In this way, the UrlBuilder can be instantiated as follows:

C#
UrlBuilder builder = new UrlBuilder(this);

Apart from these additional constructors, the public interface to the UrlBuilder class remains pretty much the same as before, but there’s a lot more going on under the hood this time. In this version, I’ve derived a new class from NameValueCollection called QueryStringDictionary. While this was not strictly necessary, it provided a cleaner separation and better organization of the class internally. It also allows the addition of more properties and methods on the collection if desired. When I first wrote this class, I used a StringDictionary instead of a NameValueCollection. StringDictionary is ideal for some scenarios but, as one reader pointed out, has some annoying limitations. It does however also provide some useful methods, such as ContainsKey and ContainsValue which are not present in NameValueCollection. Fortunately these are easy to implement, so I have added them to QueryStringDictionary.

As mentioned before, there are two implementations of IQueryStringEncoder provided with the sample code. The first implementation can be seen inside the UrlBuilder class file and is the DefaultEncoder. This is the most basic type of encoder and in fact doesn’t provide any encoding. In other words, this class used with the default encoder will behave in exactly the same way as the original UrlBuilder. The DefaultEncoder is there because it provides a way to implement encoding in a consistent manner internally. The second type of encoder, Base64Encoder, does as expected, by encoding and decoding the QueryString using Base64 encoding. You can find the actual Base64 methods in the supplied Base64 class. Base64 is not necessarily the best form of encoding, but it provides a level of obfuscation sufficient to befuddle most users and I figure, heck, if it’s good enough for ViewState encoding, it’s good enough for UrlBuilder. The beauty of this model is that any two-way encoding can be used, so if you are keener to implement TripleDES or Rijndael encryption or something a little more custom, that’s also completely doable, just implement the interface, pass into the constructor and you’re set.

UrlBuilder by example

Moving on, let’s look now at what happens when a new UrlBuilder is first instantiated using an encoder. For simplicity, let’s use the constructor which takes a Page object and an encoder.

C#
UrlBuilder builder = new UrlBuilder(this, new Base64Encoder());

Internally, the UrlBuilder gets its QueryString value from the base UriBuilder Query property. Fortunately the UriBuilder takes care of this for us, so no tedious string parsing is required. The internal QueryStringDictionary is then initialized and populated, but first we need to check if the QueryString is encoded:

C#
if(_encoder.Prefix != string.Empty && 
               query.StartsWith(_encoder.Prefix)) {
  query = query.Substring(_encoder.Prefix.Length);
  query = _encoder.Decode(query);
}

If it is, we decode it by calling Decode() on the interface. Next, the QueryStringDictionary is populated using some basic string operations.

C#
string[] pairs = query.Split(new char[]{'&'});
foreach(string s in pairs) {
  string[] pair = s.Split(new char[]{'='});
  this[pair[0]] = (pair.Length > 1) ? 
                           pair[1] : string.Empty;
}

Once the UrlBuilder object is created, the developer is free to manipulate the URL as required. For example:

C#
builder.Host = "www.gibbons.co.za";
builder.Path = "archive/2005";

It is interesting to note that a forward slash “/” is optional at the start of the Path string. Either way, the base UriBuilder class will return a correctly formed URI. Unfortunately, the UriBuilder class provides no way to get or set only the page name portion of the URI, so I added a new property called PageName which allows you to set only the name of the required page.

C#
builder.PageName = "06.aspx";

So far, apart from the PageName property, this is nothing new; all this can already be done with the base UriBuilder class. The real usefulness of UrlBuilder becomes apparent when we want to manipulate QueryString parameters:

C#
builder.QueryString["foo"] = "bar";
builder.QueryString["a"] = "A";
builder.QueryString["b"] = "B";

If the QueryStringDictionary already contains a parameter contained in the URI passed to the constructor, the value of the parameter will be overwritten with the new value. If the parameter doesn’t already exist, it is appended to the QueryString generated. In addition, all the properties and methods of the internal NameValueCollection object used to store the QueryString pairs are made available to the user. You could, for example, remove one of the parameters:

C#
builder.QueryString.Remove("foo");

Or, using the extended methods, check if the collection contains a given key or value:

C#
builder.QueryString.ContainsKey("foo");
builder.QueryString.ContainsValue("bar");

Internally, not much is going on at this point, the interesting stuff really happens when we consume the URL by calling ToString() or Navigate() (which calls ToString() anyways).

For completeness, I’ve added a format parameter overload to ToString() that takes a value of either "e" or "p" and outputs an encoded string, or plain text string respectively. Encoded strings are the default, so specifying no format parameter also outputs an encoded string. The actual work is done in the QueryStringDictionary ToString() implementation which encodes the QueryString by calling Encode() on the interface.

This may all be quite hazy at this point so it might be best to summarize all this with a simple worked example, which can also be found in the attached download.

C#
string url = 
    "http://localhost/CraigLabs.Web/Default.aspx?a=1&b=2&c=3";

UrlBuilder builder = new UrlBuilder(url, 
                         new Base64Encoder());

builder.QueryString["d"] = "4";
builder.QueryString["e"] = "5";
builder.QueryString["f"] = "6";

/*
Outputs:
http://localhost/CraigLabs.Web/Default.aspx?
                    ~YT0xJmY9NiZkPTQmZT01JmI9MiZjPTM=
*/
string encodedUrl = builder.ToString();

/*
Outputs:
http://localhost/CraigLabs.Web/Default.aspx?
                             a=1&f=6&d=4&e=5&b=2&c=3
*/
string plaintextUrl = builder.ToString("p");

There are many scenarios where UrlBuilder can be useful; I frequently use it in my Page base class. In its simplest form, the following Page base class will suffice to make your life considerably easier:

C#
public class PageBase : System.Web.UI.Page {
  UrlBuilder _pageUrl;
  public UrlBuilder PageUrl {
    get {
      return _pageUrl;
    }
    set {
      _pageUrl = value;
    }
  }

  protected override void OnInit(EventArgs e) {
    _pageUrl = new UrlBuilder(Page);
    base.OnInit(e);
  }
}

Happy coding.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralRe: need help on your builder Pin
tengtium21-Feb-06 13:18
tengtium21-Feb-06 13:18 
AnswerRe: need help on your builder Pin
lotuspro23-Feb-06 22:34
lotuspro23-Feb-06 22:34 
GeneralRe: need help on your builder Pin
tengtium24-Feb-06 18:41
tengtium24-Feb-06 18:41 
GeneralRe: need help on your builder Pin
lotuspro25-Feb-06 4:48
lotuspro25-Feb-06 4:48 
QuestionHow decode querystring parameters Pin
manuelu30-Oct-05 9:53
manuelu30-Oct-05 9:53 
AnswerRe: How decode querystring parameters Pin
lotuspro30-Oct-05 23:27
lotuspro30-Oct-05 23:27 
Generalanother constructor Pin
capicu12-Oct-05 21:59
capicu12-Oct-05 21:59 
GeneralRe: another constructor Pin
lotuspro12-Oct-05 23:31
lotuspro12-Oct-05 23:31 
GeneralRe: another constructor Pin
capicu13-Oct-05 0:31
capicu13-Oct-05 0:31 
Generalreason for no PageName in base UriBuilder Pin
Eric Newton8-Oct-05 4:47
Eric Newton8-Oct-05 4:47 
GeneralRe: reason for no PageName in base UriBuilder Pin
lotuspro11-Oct-05 0:35
lotuspro11-Oct-05 0:35 
GeneralNameValueCollection Pin
Ivan A. Gusev31-Aug-05 20:11
Ivan A. Gusev31-Aug-05 20:11 
AnswerRe: NameValueCollection Pin
lotuspro5-Sep-05 5:46
lotuspro5-Sep-05 5:46 
GeneralRe: NameValueCollection Pin
Ivan A. Gusev5-Sep-05 21:20
Ivan A. Gusev5-Sep-05 21:20 
AnswerRe: NameValueCollection Pin
lotuspro6-Sep-05 0:45
lotuspro6-Sep-05 0:45 
GeneralRe: NameValueCollection Pin
mark4asp4-May-07 0:43
mark4asp4-May-07 0:43 
GeneralBase64 and URLs Pin
Jacob Slusser24-Aug-05 16:43
Jacob Slusser24-Aug-05 16:43 
GeneralRe: Base64 and URLs Pin
lotuspro25-Aug-05 0:10
lotuspro25-Aug-05 0:10 
GeneralRe: Base64 and URLs Pin
gsimp7-Oct-05 8:56
gsimp7-Oct-05 8:56 
GeneralRe: Base64 and URLs Pin
lotuspro11-Oct-05 0:38
lotuspro11-Oct-05 0:38 
GeneralRe: Base64 and URLs Pin
Mel Grubb II20-Feb-06 3:18
Mel Grubb II20-Feb-06 3:18 
AnswerRe: Base64 and URLs Pin
lotuspro20-Feb-06 4:00
lotuspro20-Feb-06 4:00 
GeneralRe: Base64 and URLs Pin
Mel Grubb II20-Feb-06 4:21
Mel Grubb II20-Feb-06 4:21 
AnswerRe: Base64 and URLs Pin
lotuspro20-Feb-06 4:40
lotuspro20-Feb-06 4:40 
GeneralRe: Base64 and URLs Pin
Dewey10-Apr-07 15:10
Dewey10-Apr-07 15:10 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.