Reign in your web parameters

As web developers we are tempted to be general about our use or the request object and submitted parameters. The temptation to access Request["someKey"] is high because it frees us from wondering whether MyPage.aspx was posted to using POST or GET and we might also convince ourselves that it’s more flexible because it means that both POST and GET would work. Well, it would do something, that’s for sure. But do we always get consistent results? Consider:

string val = Context.Request["MyKey"];

Where does val come from? HttpRequest class scans QueryString, Form, Cookies and ServerVariables of the request object, in that order. First match gets you an answer. This also means that if a query string param named “SID” exists and a form field named “SID” exists, you will get the query string value (GET). The implementation essentially is

// implementation detail
public string this[string key]
{
get
{
string returnValue = this.QueryString[key];
if (returnValue != null)
{
return returnValue;
}
returnValue = this.Form[key];
if (returnValue != null)
{
return returnValue;
}
HttpCookie cookie1 = this.Cookies[key];
if (cookie1 != null)
{
return cookie1.Value;
}
returnValue = this.ServerVariables[key];
if (returnValue != null)
{
return returnValue;
}
return null;
}
}

Note here that as a last resort, the key is looked up against the ServerVariables collection. That’s makes me a bit uneasy. Not that I ever wanted a form variable named “USER_AGENT” but now that I know it scans for it, I’ll be careful about my variable naming. Moving on, consider: string val = Context.Request.Params["MyKey"];

Where does val come from? You wouldn’t necessarily know. Params is built on first request, and accessed throughout the lifetime of the HttpRequest object. Building it is done by creating a new collection, which includes all 4 request sources, as listed below. Since it is a Collections.Specialized.NameValueCollection type, if a key exists in more than one of the 4 sources, the value would be appended as a comma separated list. So if “PID” was both a query string GET parameter (say value 123) and a form POST variable (say value 456), then (Request.Params["PID"] == "123,456") == true;

private void FillInParamsCollection()
{
//_params is the underlying collection supporing the Params property
this._params.Add(this.QueryString);
this._params.Add(this.Form);
this._params.Add(this.Cookies);
this._params.Add(this.ServerVariables);
}

What can we conclude from these observations?

  1. If you called Request.Params, even once, you now created a new collection, allocating enough memory to hold ALL parameters from the various sources. If you know what the parameter’s source is, you would be more efficient using that source collection directly. If you or your buddy working on the same code tree called Request.Params, however, the hit is taken, and subsequent references would not re-allocate collections.

  2. Precedence of parameters applies when you call Request[key], but not when you call Request.Params[key] .

  3. The effect of Params collection sporting a comma separated list is a double whammy:

    1. Upon insert, (Request.Params[“myKey”] = “myvalue”) an arraylist is created and appended to (repetitive allocation for each value).

    2. Upon assignment from Request.Params:

      1. If the string [] GetValues() method is used, the ArrayList gets converted to a string [] just for you each time (it’s not cached or preserved as an internal variable).

      2. If the string Get() is used, private static string GetAsOneString(ArrayList list), a string builder object is created just for you to concatenate the values and return them to you.

Although both methods are coded as efficiently, neither gets you a reference to an existing object, and both have to create another object and copy data to it on the fly each time. For that reason, it’s less efficient than if you knew exactly the source of your parameter and used that collection as the source.

This issue can manifest if your application combines things like flash movies, remote static HTML forms submitting to your site (think affiliate marketing programs) and various hard coded links which were created to a specific form from menus, site maps and other corners of your application. When you work with forms designed by visual studio and sue asp form controls you generally don’t encounter this because then you have access to strongly typed properties. More often than not I have run across a hybrid of asp form controls and hand coded links from CMS parts of your app or other rouge text links so it’s worth knowing and paying attention. To reign in the parameters and ensure no rouge parameters exist ever, I’d recommend creating a utility class that would wrap the Request object and use a set of well known parameters only which would encompass all usable parameter names. In conclusion, for both efficiency and un-ambiguity you would benefit from using concise parameter sources such as Request.QueryString["name"] and Request.Form["name"], and not rely on the “catchall” of Params or Reqest["name"] as shorthand. If you do find the need, inspect and ensure that parameter names do not collide so that you don’t end up with a peculiar value.

/// ParamMarshal is a sample utility which wraps the Request object.
/// It provides an easy way to eliminate ambiguity and define the
/// source of POST and GET parameters for large web projects so that
/// no parameter collision happens. Search your solution for any
/// reference to the "Request[" or "Request." and replace it with a
/// call to ParamMarshal.GetValue()
///
namespace NH.Web.Utilities
{
public class ParamMarshal
{
public static string GetValue(WellKnownParam param)
{
string result = string.Empty;
switch (param)
{
// the first 5 are POST variables. No ambiguity.
case WellKnownParam.FirstName:
case WellKnownParam.LastName:
case WellKnownParam.Password:
result = HttpContext.Current.Request.Form[param.ToString()];
break;
// the following 2 are GET variables. No ambiguity either.
case WellKnownParam.AffiliateID:
case WellKnownParam.Keywords:
result = HttpContext.Current.Request.QueryString[param.ToString()];
break;
default:
/// this should never happen., because you should
/// take care of every member of the WellKnownParameter
/// Specifically, do NOT put
/// return HttpContext.Current.Request[param.ToString()]..
throw new Exception(
"Programmer forgot to handle " + param.ToString());
break;
}
return result;
}
public enum WellKnownParam
{
FirstName,
LastName,
Password,
AffiliateID,
Keywords
}
}
}