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

UrlBuilder Part II - Encoded QueryStrings

By , 29 Sep 2005
 

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:

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.

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

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

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.

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:

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.

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:

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.

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:

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:

builder.QueryString.Remove("foo");

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

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.

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:

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

About the Author

lotuspro
Web Developer
United Kingdom United Kingdom
Member
No Biography provided

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   
GeneralStrait example [modified]memberzitun28 May '08 - 3:00 
string uriPath = "http://mySite.com/myPage.aspx?myParam=abcdeféèàç!&myParam2=ù$()";
CraigLabs.Web.Code.UrlBuilder builder = new CraigLabs.Web.Code.UrlBuilder(uriPath, new CraigLabs.Web.Code.Base64Encoder());
 
string myUriPathEncoded = builder.ToString(); 
//result = "http://mysite.com/myPage.aspx?~bXlQYXJhbT1hYmNkZWbDqcOow6DDpyEmbXlQYXJhbTI9w7kkKCk*"

CraigLabs.Web.Code.UrlBuilder myBuilderDecoded = (new CraigLabs.Web.Code.UrlBuilder(myUriPathEncoded, new CraigLabs.Web.Code.Base64Encoder()));
 
string param1 = myBuilderDecoded.QueryString.Values[0]; //result : abcdeféèàç
string param2 = myBuilderDecoded.QueryString.Values[1]; //result : ù$()
 
It took me one hour to understand the code. Here is a strait and easy example if you want to scramble your query and put scary characters in it.
------------------------
 
Warning : As it has been already post becareful to modify Base64Encoder.cs to take in account the special caracters : "=", "/", etc...
 
So I changed two lines in the Base64Encoder.cs codefile :
return Base64.Encode(s).Replace("=","*").Replace("/","_").Replace("+","-");
 
and
 
return Base64.Decode(s.Replace("*", "=").Replace("_", "/").Replace("-", "+"));
 
-----------------------
 
Last but not least I remove the line
query = HttpUtility.UrlDecode(query);
This line bring problems with the "+" and is not really necessary
 
-----------------------
 
Here is one more thing. When you pass some strange caracters as "the space" then it's automatically change by "%20". To overcome this probleme change the code
_queryString = new QueryStringDictionary(base.Query, _encoder);
(in the UrlBuilder.cs) by
_queryString = new
QueryStringDictionary(System.Text.RegularExpressions.Regex.Match(this.Uri.ToString(), "\\?.*").Value, _encoder); 
Thanks for your code.
 
Regards,
 
Zitun
 
modified on Thursday, May 29, 2008 9:17 AM

GeneralRe: Strait examplememberOm Rudi11 Mar '09 - 0:45 
it won't work, when you meet double '*'
better use '^' instead...
 
.:. Apakek System .:.

GeneralWhy not use Request.QueryString to initilize QueryStringDictionarymemberfewfewfewfgwew8 Jan '07 - 14:05 
Hello,
 
I am using your class mainly for building non-encode querystring, I am looking at performance area. One thing I do not understand:
You initilize the QueryStringDictionary by passing in the string query (para), why not directly using the Request.QueryString, which already a NameValueCollection, and already contain the value format that your Initialise trying to archieve. Please let me know if I miss anything.
 
Thanks,
-------------
private void Initialise(string query)
{
if (query == string.Empty || query == null)
return;

if (_allowedkeys == null || _allowedkeys.Count <= 0)
return;
 
this.Clear(); //clear the collection
query = query.Substring(1); //remove the leading '?'
 
query = HttpUtility.UrlDecode(query); //not actually necessary if using a "~" prefix
 
if (_encoder.Prefix != string.Empty && _encoder.Prefix != null && query.StartsWith(_encoder.Prefix))
{
query = query.Substring(_encoder.Prefix.Length);
query = _encoder.Decode(query);
}
 
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;
}
}
AnswerRe: Why not use Request.QueryString to initilize QueryStringDictionarymemberlotuspro8 Jan '07 - 21:33 
There are 2 Initialise methods. One resides in UrlBuilder, the other in QueryStringDictionary. The method in QueryStringDictionary takes a string because it is called internally from UrlBuilder using the only property available on the base class UriBuilder, that being .Query (a string). The whole point is that UriBuilder contains no QueryString collection so the class builds its own one.
GeneralMissing Relative URL optionmemberMd. Jannatul Ferdous13 May '06 - 2:58 
Hi,
 
Excellent artical but it always take/return absolute URL. i did not find any option for relative URL. if u use this helper class to navigate in the frame all session variable data will be flash.
 
if you have any answer plz give it as soon as possible.
 
thanks in advanced
 

 
Md. Jannatul Ferdous
Sr. Software Engineer

AnswerRe: Missing Relative URL optionmemberlotuspro14 May '06 - 10:32 
Hi,
 
Thanks for your comments. I'm not sure if I understand your question exactly, but I haven't considered adding support for relative URL's, though I am sure this is possible to do. Sorry I can't be more help at this time.
Generalneed help on your buildermembertengtium19 Feb '06 - 14:00 
on your example you set up the querystring and assuming you pass it to redirect method....
 
how can the receiving page process the querystring... i tried your example and create a receive form... when i try to get the value of "a".. no value is retrieve...
 
can you give me an example how can i retrieve the value in the receiving page...
 
tnx..
AnswerRe: need help on your buildermemberlotuspro19 Feb '06 - 22:22 
The receiving page need only create a new instance of UrlBuilder using the Page object constructor overload, as follows:
 
UrlBuilder builder = new UrlBuilder(Page);
 
UrlBuilder uses the page object to populate the object with the querystring parameters passed.
GeneralRe: need help on your buildermembertengtium20 Feb '06 - 14:27 
i did try it.. i just try your example program.
 
i called your navigate method like this...
 
// in Default.aspx.
// i added a the second form Webform1.aspx
string url = "http://localhost/CraigLabs.Web/Webform1.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";
 
litBase64EncoderEncoded.Text = builder.ToString();
litBase64EncoderPlaintext.Text = builder.ToString("p")
 
builder.Navigate();
 

// in the receiving form
 
UrlBuilder _pageUrl;
public UrlBuilder PageUrl 
{
	get 
	{
		return _pageUrl;
	}
	set 
	{
		_pageUrl = value;
	}
}
 
override protected void OnInit(EventArgs e)
{
//
// CODEGEN: This call is required by the ASP.NET Web Form Designer.
//
_pageUrl = new UrlBuilder(HttpContext.Current.Request.Url);
InitializeComponent();
base.OnInit(e);
}
 
in receiving page load method..
i added a control to show the querystring value.
 
private void Page_Load(object sender, System.EventArgs e)
{
   this.Label1.Text = this.PageUrl.QueryString["a"];
}
 
it doesn't show up...
 
can you please try it.. or can you give some example...

AnswerRe: need help on your buildermemberlotuspro20 Feb '06 - 22:39 
What are you actually seeing in the browser address bar in the receiving page? If the url is correct there, then your problem is not on the sending page.
 
If the url is correct, make sure:
 
HttpContext.Current.Request.Url in OnInit() on the receiving page is returning the correct thing. You can easily debug this by stepping through the code on the sending and receiving page.
GeneralRe: need help on your buildermembertengtium21 Feb '06 - 13:18 
yes, i did.. on your given example, i just add a second form(the recieving form) and on your Default.aspx i modify the url to point to the receiving form(Webform1)..
 
string url = "http://localhost/CraigLabs.Web/Webform1.aspx?a=1&b=2&c=3";
 
and call the Navigate method...
 
builder.Navigate();
 

on the recieving form(Webform1).. i added your property....
 
UrlBuilder _pageUrl;
public UrlBuilder PageUrl 
{
	get 
	{
		return _pageUrl;
	}
	set 
	{
		_pageUrl = value;
	}
}

override protected void OnInit(EventArgs e)
{
//
// CODEGEN: This call is required by the ASP.NET Web Form Designer.
//
_pageUrl = new UrlBuilder(HttpContext.Current.Request.Url);
InitializeComponent();
base.OnInit(e);
}
 
and on page load
 
this.Label1.Text = this.PageUrl.QueryString["a"];
 
btw, i added a Label control
 
AND I DIDN'T WORK...
 
PLEASE CAN YOU GIVE A RUNNING EXAMPLE....

AnswerRe: need help on your buildermemberlotuspro23 Feb '06 - 22:34 
Aah, I see now what the problem is.
 
When you create an instance of UrlBuilder on the receiving page, you need to specify what encoder to use, as follows:
 
_pageUrl = new UrlBuilder(HttpContext.Current.Request.Url, new Base64Encoder());
 
Hope that solves your problem.
GeneralRe: need help on your buildermembertengtium24 Feb '06 - 18:41 
do the complete url is required?
url= http://localhost/CraigLabs.Web/Webform2.aspx?a=1"
 
when i try to use it again and set the url string to:
 
url = "Webform2.aspx?a=1"
 
and call the navigate method... it doesn't redirect to the specified form (Webform2.aspx) and usually we reference the form as shown above, in the same directory...
 

 

GeneralRe: need help on your buildermemberlotuspro25 Feb '06 - 4:48 
If you wish to redirect to the same page (i.e. the same url), then you shouldn't have to do anything other than call builder.Navigate();
QuestionHow decode querystring parametersmembermanuelu30 Oct '05 - 9:53 
Hi, it is a great article, the step of send the parameters encoded is fantastic but How can I decode the parameters?
 
I am using the urlbuilder beetwen two pages, one sender and one receiver. And my doubt is on the receiver How can I get the parameters decoded 64 base.
 
Thanks in advanced.

 
B
AnswerRe: How decode querystring parametersmemberlotuspro30 Oct '05 - 23:27 
Hi,
 
Thanks for the feedback. I'm not sure if I understand your question correctly, but there's actually no decoding to be done yourself. The UrlBuilder will automatically decode the querystring on the recieving page by calling Decode() on the IQueryStringEncoder you passed in. As long as your encoding is two-way, you shouldn't have any problems with this. The easiest way to achieve this, is to implement a page base class (as mentioned at the end of the article) which will create a new UrlBuilder on each page. Alternatively, you can just manually create one in the Page_Load(). I hope that answers your question.
Generalanother constructormembercapicu12 Oct '05 - 21:59 
hi!
 
great work, I love the UrlBuilder!
but I think this constructor would also be nice:
 
public UrlBuilder(System.Web.HttpRequest request) : base(request.Url) 
{
   Initialise();
}
 
it's useful if you get the Request-object from System.Web.HttpContext.Current
 
florian
GeneralRe: another constructormemberlotuspro12 Oct '05 - 23:31 
Thanks Big Grin | :-D , glad you find it useful.
 
I think your constructor would be a useful addition to the class as one does not always have a Page object to work with. You could of course instantiate using:
 
new UrlBuilder(HttpContext.Current.Request.Url);
 
...but it's probably more succinct to just pass in the Request object.
 
Thanks for the feedback.
GeneralRe: another constructormembercapicu13 Oct '05 - 0:31 
didn't realize that I could use this constructor Smile | :)
 
so mine isn't really necessary
 
anyway! great work!

Generalreason for no PageName in base UriBuildermemberEric Newton8 Oct '05 - 4:47 
The reason why there's no "pagename" portion of the uri is because the entire string after the http://server:port is the "resource to retrieve", including the PageName.
 
There's a lot of URL schemes that wouldn't use a pagename at all... For example, consider ebay's isapi filter they have... technically, your pagename property would return "ebayIsapi.dll" but the true pagename is in the second parameter. Granted, this is eBay's scheme, but you get the point.
GeneralRe: reason for no PageName in base UriBuildermemberlotuspro11 Oct '05 - 0:35 
Point well made, but I think most of the time, having a PageName property is benficial, eventhough it isn't applicable to some URL schemes. Of course the class could be easily adapted to alternate schemes by some basic changes to the PageName string parsing.
GeneralNameValueCollectionmemberJohnnyGusev31 Aug '05 - 20:11 
Could you describe the problem with NameValueCollection? I'm using modified version of your code, and it works fine with NameValueCollection. StringDictionary stores all keys in lower case, and sometimes it produces unreadable URLs (something like MyPage.aspx?firstparameter=some_value instead of ?FirstParameter=some_value).
AnswerRe: NameValueCollectionmemberlotuspro5 Sep '05 - 5:46 
NameValueCollection is readonly. When I first wrote a version of UrlBuilder I was using a NameValueCollection to store the QueryString values, but found I could only read the collection, not write to it. Have you tried adding new keys? In any case, StringDictionary as you correctly point out converts all keys to lowercase, this behaviour is documented although I can't really see why this is by design. When dealing with URL's, I never need to know or see if the key is uppercase, so I don't see this as a problem, however you can always use a Hashtable if you prefer, just change the base class of QueryStringDictionary to Hashtable. A Hashtable will maintain the case of the dictionary keys. It also has the arguable advantage of being able to take a value of any type, although the implementation of QueryStringDictionary.ToString() would have to be modified to support this. I'll run some tests with this and if a Hashtable turns out to be better on the whole, I'll modify the design and the article.
 
Many thanks for your feedback.
GeneralRe: NameValueCollectionmemberJohnnyGusev5 Sep '05 - 21:20 
I have strong doubts that System.Collections.Specialized.NameValueCollection is read-only. This code snippet works fine:
 
NameValueCollection c = new NameValueCollection();
c.Add("k", "v");
c["k"] = "V";
 
Sure, you can make this collection read-only. To do it you should inherit from System.Collections.Specialized.NameValueCollection and override IsReadonly protected property of this class.
AnswerRe: NameValueCollectionmemberlotuspro6 Sep '05 - 0:45 
I see now what is happening. Originally I had passed the NameValueCollection from Request.QueryString into my class, which is readonly. I did some playing around with the code of my class and used a NameValueCollection instead of a StringDictionary. everything seems to be working correctly, however some of the methods which were present in StringDictionary (and Hashtable) are missing in NameValueCollection like ContainsKey and ContainsValue, so i've added those to QueryStringDictionary. I guess you've already done much of this yourself, but if you are interested in seeing the modified code, i'll gladly mail it to you. I'll do some more testing and rewrite part of the article if all still looks good.

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 29 Sep 2005
Article Copyright 2005 by lotuspro
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid