tags:

views:

1285

answers:

7

I'm curious what everyone does for handling/abstracting the QueryString in ASP.NET. In some of our web apps I see a lot of this all over the site:

int val = 0;
if(Request.QueryString["someKey"] != null)
{
val = Convert.ToInt32(Request.QueryString["someKey"]);
}

What are some better ways to handle this grossness?

+6  A: 

One thing is you're not capturing blank values here. You might have a url like "http://example.com?someKey=&anotherKey=12345" and in this case the "someKey" param value is "" (empty). You can use string.IsNullOrEmpty() to check for both null and empty states.

I'd also change "someKey" to be stored in a variable. That way you're not repeating literal strings in multiple places. It makes this easier to maintain.

int val = 0;
string myKey = "someKey";
if (!string.IsNullOrEmpty(Request.QueryString[myKey]))
{
    val = int.Parse(Request.QueryString[myKey]);
}

I hope that helps!

Ian

Ian Suttle
this is likely to throw an exception!
Andreas Niedermair
you'd certainly want to include error handling. That's a given isn't it? :)
Ian Suttle
+13  A: 

I tend to like the idea of abstracting them as properties. for example:

        public int age { 
        get
        {
            if (Request.QueryString["Age"] == null)
                return 0;
            else
                return int.Parse(Request.QueryString["Age"]);                                    
        }
    }

You could add more validation if you wanted to. But I tend to like wrapping all of my query string variables this way.

EDIT: --- Also as another poster pointed out that you have to create these properties on every page. My answer is no you do not. You can create these properties in a single class that you can call "QueryStrings" or something. Then you can instantiate this class in every page where you want to access your query strings, then you can simply do something like

var queryStrings = new QueryStrings()
var age = queryStrings.age;

This way you can encapsulate all of the logic for accessing and handling each type of query variable in a single maintainable location.

EDIT2: --- And because it is an instance of the class, you could also use dependency injection to inject the QueryStrings class in every place you are using it. StructureMap (http://structuremap.sourceforge.net) does a good job of that. This also allows you to mock up the QueryStrings class and inject that if you wanted to do automated unit testing. It is much easier to mock this up than ASP.Net's Request object.

Roberto Sebestyen
Added bonus here is that it tells future devs exactly what to expect from the query string when they first sit down with your code.
Joel Coehoorn
Yes thank you for noticing that. That is definitely the intention.
Roberto Sebestyen
+1 - a grand way to handle it
nailitdown
+1 - going through and implementing exactly this technique in a main framework at the moment, in part, to help try and get newer devs up to speed.
Pat
this is likely to throw an exception!
Andreas Niedermair
I'm in favor of this solution. I put static properties in a static class that I can use anywhere on the site and throw a try catch around where I convert the string to a specific type. You can use this same approach for accessing the Session or Cache objects as well.
Nick
dittohole: You can add exception handlers if you wish in this solution. This example is only meant to be a "blueprint" for the idea. You can implement it whichever way you want to.
Roberto Sebestyen
+1  A: 

Write some sort of a helper method (library) to handle it...

public static void GetInt(this NameValueCollection nvCol, string key, out int keyValue, int defaultValue)
{
    if (string.IsNullOrEmpty(nvCol[key]) || !int.TryParse(nvCol[key], out keyValue))
        keyValue = defaultValue;
}

Or something along those lines...

Charlino
But the programmer would have to know the expected type the query string should be returned as. (To know which function to call) If you wrap it using a property getter like in the answer i gave, then all you would have to know is the name of the property you are after. Intellisense will do the rest.
Roberto Sebestyen
Yea, just like the programmer would have to know the expected type of the query string to setup a property. My solution is to stop the repeated yucky "if" statements all over the show as described in the question.What they do with the code after that is up to them - hey, put it in a property!
Charlino
but once the property is set up, that is it its done. other programmers that come after will not have to guess what kind of data is in the query string and what specific validation should occur. you can wrap all of that up in the property.
Roberto Sebestyen
You obviously are too caught up in your own answer to see that mine is tackling a different issue and the fact that my answer could compliment yours quite nicely.
Charlino
A: 

We've been using constants to keep all of these "loose" keys in a central location:

public class Constants
{
  public class QueryString
  {
    public const string PostID = "pid";
    public const string PostKey = "key";
  }
  public class Cookie
  {
    public const string UserID = "mydomain.com-userid";
  }
  public class Cache
  {
    public const string PagedPostList = "PagedPostList-{0}-{1}";
  }
  public class Context
  {
    public const string PostID = "PostID";
  }
  public class Security
  {
    public const RoleAdministrator = "Administrator";
  }
}

That way, you easily access the constants you need with:

public void Index()
{
  if (Request[Constants.QueryString.PostKey] == "eduncan911")
  {
    // do something
  }
}

public object GetPostsFromCache(int postID, int userID)
{
  String cacheKey = String.Format(
      Constants.Cache.PagedPostList
      , userID
      , postID);
  return Cache[cacheKey] as IList<Post>;
}
eduncan911
A: 

I'm with the poster who suggested the helper methods (I would comment on his, but can't yet). Someone else disagreed with him in favor of creating properties, but my answer to that is that it doesn't gracefully handle the issue of checking for nulls or invalid formatting, etc. If you have helper methods, all that logic can be written once and centralized.

If you have a lot of pages, adding properties for each might be more time consuming than it is worth. But that's obviously just a preference, so to each his/her own.

One cool thing you could improve on the other poster's helper method is to make the out parameter a reference parameter instead (change out to ref). That way you can set a default value for the property, in case it is not passed. Sometimes you might want optional parameters, for instance -- then you can have it start with some default value for those cases where the optional parameter is not explicitly passed (easier than having a default value passed separately in). You can even add an IsRequired boolean parameter at the end, and have it throw an exception if the bool is set to true and the parameter is not passed. That can be helpful in many cases.

x4000
You don't have to create the properties on every page. Only a single class containing all of the properties you expect to use. You can then instantiate that class in the pages you are using it in to access query parameters.
Roberto Sebestyen
Well, that presumes that you have the same querystring parameters on each page, though. In a large system, you're going to have a vast number of different ones, same as you have different parameters to different methods. Seems like consistency is a good thing.
x4000
A: 

hm ... i wrote a util some time ago ... not perfect, but maybe interesting - sorry, the entry is in german!

my blog :)

Andreas Niedermair
+2  A: 

Here's what I came up with. It uses generics to return a strongly-typed value from the QueryString or an optional default value if the parameter isn't in the QueryString:

/// <summary>
 /// Gets the given querystring parameter as a the specified value <see cref="Type"/>
 /// </summary>
 /// <typeparam name="T">The type to convert the querystring value to</typeparam>
 /// <param name="name">Querystring parameter name</param>
 /// <param name="defaultValue">Default value to return if parameter not found</param>
 /// <returns>The value as the specified <see cref="Type"/>, or the default value if not found</returns>
 public static T GetValueFromQueryString<T>(string name, T defaultValue) where T : struct
 {
  if (String.IsNullOrEmpty(name) || HttpContext.Current == null || HttpContext.Current.Request == null)
   return defaultValue;

  try
  {
   return (T)Convert.ChangeType(HttpContext.Current.Request.QueryString[name], typeof(T));
  }
  catch
  {
   return defaultValue;
  }
 }

 /// <summary>
 /// Gets the given querystring parameter as a the specified value <see cref="Type"/>
 /// </summary>
 /// <typeparam name="T">The type to convert the querystring value to</typeparam>
 /// <param name="name">Querystring parameter name</param>
 /// <returns>The value as the specified <see cref="Type"/>, or the types default value if not found</returns>
 public static T GetValueFromQueryString<T>(string name) where T : struct
 {
  return GetValueFromQueryString(name, default(T));
 }
Dan Diplo