ASP.NET's 260 Length Path Segment Should Be Enough For Anyone (???)

Just last week, I discovered a long known, highly irritating limitation of ASP.NET with regard to the way it handles pathinfo. Worst of all, the problem is impossible to reproduce locally using the Cassini web server that is build into VisualStudio. After banging my head against the wall, I think I came up with a pretty good solution that worked in my particular example, which I will refer to as “Check Box Hell”.

A Little Bit of Background

So, you’re all proud of yourself because you managed to build a filtering control in .NET that is easy to use anywhere and can create path info URLs so you can pass along the results of a filter to anyone easily (rather than POSTed). Let’s assume the part of your filter tool that does path info stuff looks like this:

	// pseudo-ish 				string id, value; 				string pathInfo = ""; 				foreach (Control control in wrapperControl.Controls){ 				id = control.ID; 				if (control is TextBox && !string.IsNullOrEmpty(((TextBox)control).Text)){ 				value = ((TextBox)control).Text; 				} 				else if (control is DropDownList && ((DropDownList).SelectedIndex > 0){ 				value = ((DropDownList)control).SelectedValue 				} 				... // other controls  				if (!string.IsNullOrEmpty(value)){ 				pathInfo += "/" + id + "=" + Server.UrlPathEncode(value); 				} 				} 				

It’s fairly simple, and it’ll create your path segments that look like this: /txtZipCode=19127/drpState=PA. You test the code locally using the VisualStudio Cassini web server and it works like a champ. Then, you deploy it to your staging server and it is broke like a joke. How could this be? Read on for the solution.

And the part that decodes the path info:

	// required code 				private string GetPathInfoValue(string id, string pathInfo) 				{ 				Regex regPathInfo = new Regex("/" + id + "=(.*?)/"); 				Match match = regPathInfo.Match(pathInfo); 				string value = null; 				if (match.Success) 				value = match.Groups[1].Value; 				return value; 				}  				foreach (Control control in this.divWrapper.Controls) 				{ 				string value = GetPathInfoValue(control.ID, Request.PathInfo); 				if (!string.IsNullOrEmpty(value)) 				{ 				SetControlValue(control, value); 				} 				} 				

I have check boxes coming out of my ears!!

Handling most control styles should be pretty straightforward. You have your text box, check box, radio button, text area (although, is that realistic in a filter?), select list. One thing that isn’t too obvious is the check box list. Remember this is a control in .NET. The simple solution is to CSV the values! Simple until your list of checkboxes gets too big!! Wait, what? Most web servers can handle a thousand characters in your path segments, including IIS. However, ASP.NET limits you to 260. I know, it’s a shame.

So how do we handle the Check Box List with, say, 100 items? Some answers on forums you might receive is to “POST” rather than “GET”. You’re an HTTP wizard, you know you’re not “POST”ing anything, in REST speak. You also know that you want to be able to pass this URL to and easily create a PDF of your website. Or to just generally link it easily, say for an RSS reader if it’s a filtered RSS feed.

Computers to the rescue!

ShrinkageSomehow I knew computers were involved. Logic and common sense will tell you that since it’s too big of a path segment, you need to make it smaller. Generally, in computer speak, there’s one way: compression. Our initial reaction, throwing your path segment in a cold pool, won’t work.

No cold pool?  My idea pool is exhausted

Since you’re trying to pass along selected values, why not pass along the inverse, values that aren’t selected, if the list of selected values is too large for a path segment? Since you know you’re screwed if you can’t send along half the list, that will be our cutoff point. Our code now looks like this:

	// in our list of "if" statements, this is when we determine the control is a checkbox list 				CheckBoxList cbl = (CheckBoxList)control; 				bool allSelected = false;  				int selectedCount = 0, allCount = cbl.Items.Count;  				foreach (ListItem item in cbl.Items) 				{ 				if (item.Selected) selectedCount++; 				}  				// if more than half are selected, use inverse 				bool useInverse = allCount / 2 < selectedCount; 				bool selectAll = allCount == selectedCount;  				if (!selectAll) 				{ 				if (useInverse) value = "~";  				foreach (ListItem item in cbl.Items) 				{ 				if (item.Selected != useInverse) 				value += item.Value + ","; 				} 				if (value.Length > 1) value = value.Substring(0, value.Length - 1); 				} 				else value = "all";  				/// later on, add "/" + id + "=" + Server.UrlPathEncode(value) 				

And for processing the path segment that we get:

	CheckBoxList cbl = (CheckBoxList)control; 				if (value != "all") 				{ 				bool inverse = value.StartsWith("~"); 				if (inverse) value = value.Substring(1); // trim "~" from the string 				string[] ids = value.Split(',');  				if (inverse) 				{ 				foreach (ListItem item in cbl.Items) item.Selected = true; 				}  				foreach (string id in ids) 				{ 				ListItem item = cbl.Items.FindByValue(id); 				if (item != null) item.Selected = !inverse; 				} 				} 				else 				{ 				foreach (ListItem item in cbl.Items) item.Selected = true; 				} 				

Any method is not without problems

Some caveats to this method: 1. If half of your list minus one element makes the path info longer than that allowed by ASP.NET, it won’t help. This method is golden, though, if including 75% or more of your full list of CSVs is too long. So you can now pass along the whole list, or specify 75-100% of it, which you couldn’t before.

2. If, in some admin interface, someone were to create another item between the time that you generated a CSV list of the item IDs, and when a request was made back with the inversion url style (~1,2,3,4), your included list now includes the new one, which may or may not be desired behavior.

To see a working example of this method, download the path info code. You will have to publish it to IIS to see the desired “undesirable” behavior because Cassini does not have the problems with the path info length!

« Prev Article
Next Article »