Craig Shoemaker posted a blog entry yesterday, and point number six was about using a custom attribute to ease QueryString pains.  At my current job I work with ASP.NET WebForms, and I knew exactly what he was talking about.  I thought this was an awesome idea, and followed his link to a VB article on code project.  I converted the VB code to C#, and made some slight changes.

The Goal

Eliminate “keyboard slapping” and provide some robust functionality at the same time.  In other words, we’re trying to solve the following problems:

  1. Try to eliminate the need to access Request.QueryString directly.
  2. Try to make the implementation as streamlined as possible.
  3. Account for some possible use cases like:
    • Special Parameter Names
    • Synonymous Parameter Names

Out With The Old

As Craig pointed out in his blog, we’ve all written the same mindless QueryString code over and over.  It always amounts to the same boiler plate code, which involves indexing into the Request.QueryString object using a string key and safeguarding the entire operation.  Don’t repeat yourself.

protected string UserName

{

    get

    {

        if (Request.QueryString["UserName"] != null)

            return Request.QueryString["UserName"].ToString();

        return string.Empty;

    }

}


Would become:

[QueryStringValue]

public int UserName { get; set; }


AutoValueAttribute

I started with an abstract base class for this implementation.  The idea is that the attribute will have keys, and a way to get a value from those keys.  The idea behind using an abstract base is that we may implement other versions of the AutoValueAttribute later on.

//-----------------------------------------------------------------------------

/// <summary>

///     AutoValueAttribute

/// </summary>

public abstract class AutoValueAttribute : Attribute

{

    //-------------------------------------------------------------------------

    /// <summary>

    ///     Gets a collection of Keys to be used for

    ///     retrieving a value for the property.

    /// </summary>

    public abstract ICollection<string> Keys { get; protected set; }

 

    //-------------------------------------------------------------------------

    /// <summary>

    ///     Uses its keys to find a value.

    /// </summary>

    /// <returns></returns>

    public abstract object GetValue();

}


The QueryStringValue Attribute

Any property we wish to set from a QueryString parameter can be decorated with a QueryStringValue attribute.  The implementation is simple, supporting three specific use cases.  This attribute just holds onto values that we can use for processing later.

//-----------------------------------------------------------------------------

/// <summary>

///        QueryStringValue Attribute

/// </summary>

public class QueryStringValueAttribute : AutoValueAttribute

{

    #region Properties

 

    //-------------------------------------------------------------------------

    /// <summary>

    ///        Gets a collection of Keys

    /// </summary>

    public override ICollection<string> Keys { get; protected set; }

 

    #endregion

 

    #region Constructors

 

    //-------------------------------------------------------------------------

    /// <summary>

    ///        Constructor;  Defaults to property name

    /// </summary>

    public QueryStringValueAttribute()

    {

        Keys = new List<string>();

    }

 

    //-------------------------------------------------------------------------

    /// <summary>

    ///        Constructor;  Specifies a single key

    /// </summary>

    /// <param name="key"></param>

    public QueryStringValueAttribute(string key)

    {

        Keys = new List<string> { key };

    }

 

    //-------------------------------------------------------------------------

    /// <summary>

    ///        Constructor;  Specifies many keys

    /// </summary>

    /// <param name="keys"></param>

    public QueryStringValueAttribute(string[] keys)

    {

        Keys = new List<string>(keys);

    }

 

    #endregion

 

    #region Methods

 

    //-------------------------------------------------------------------------

    /// <summary>

    ///     Iterates through all of the Keys, and returns the first match

    /// </summary>

    /// <returns></returns>

    public override object GetValue()

    {

        object value = null;

 

        foreach (string key in Keys)

        {

            value = HttpContext.Current.Request.QueryString[key];

 

            if (value != null)

                break;

        }

 

        return value;

    }

 

    #endregion

}


I expanded on the concept of Keys while converting the VB code.  It originally used a string property named QueryStringParameter.  While this was straight forward, I thought it might be beneficial to build in the ability to handle many parameter names by using a list instead.  A simple use case is changing the parameter name, but wishing to maintain backwards compatibility.

The default constructor doesn’t specify a name.  Instead, the target property’s name will be used as the QueryString parameter key.  A second constructor accepts a single string.  The last constructor accepts an array of strings.  In this case, the first match is the one that will be used.

Calling the GetValue method will iterate through the list of keys, indexing into Request.QueryString until it finds a value.  There is one important caveat to be aware of, and that is if the default constructor was used, the list of keys will be empty.  If only attributes knew exactly which property they were decorating, it wouldn’t be a problem.  The code that consumes this method must add the property name if the attribute contains no other keys (more on that shortly).

Processing The Attributes Via Reflection

In order to make this widely available in an ASP.NET application, I created the processing method as a generic extension method.  The method is called ProcessAttributes.  It uses a constraint to limit it to System.Web.UI.Control objects.

//---------------------------------------------------------------------

/// <summary>

///     Processes all attributes

/// </summary>

/// <typeparam name="T"></typeparam>

/// <param name="ui"></param>

public static void ProcessAttributes<T>(this T ui)

    where T : System.Web.UI.Control

{

    const BindingFlags flags = BindingFlags.NonPublic

        | BindingFlags.Public | BindingFlags.Instance;

 

    PropertyInfo[] properties = ui.GetType().GetProperties(flags);

 

    foreach (PropertyInfo property in properties)

    {

        var att = property.GetCustomAttributes(true)

                          .OfType<AutoValueAttribute>()

                          .FirstOrDefault();

 

        if (att == null)

            continue;

 

        object value = GetValue(property, att);

 

        SetValue(ui, property, value);

    }

}


BindingFlags are used to get the properties of the Page.  Then, we iterate through each one.  Using the first QueryStringValueAttribute of each property,  we proceed to get the QueryString value, and set the property.

//---------------------------------------------------------------------

/// <summary>

///     Gets a value from the AutoValueAttribute

/// </summary>

/// <typeparam name="T"></typeparam>

/// <param name="ui"></param>

/// <param name="request"></param>

/// <param name="field"></param>

/// <param name="att"></param>

/// <returns></returns>

private static object GetValue(PropertyInfo property, AutoValueAttribute att)

{

    if (att.Keys.Count == 0)

        att.Keys.Add(property.Name);

 

    return att.GetValue();

}


As previously noted, there is a caveat for calling the GetValue method.  It iterates the attribute’s Keys property until it can get a value out of the QueryString.  If no key was specified in the attribute constructor, it will have a Keys.Count of 0.  In this case, we need to add the property’s name to the list of Keys.

//---------------------------------------------------------------------

/// <summary>

///     Sets the property with the extracted value

/// </summary>

/// <typeparam name="T"></typeparam>

/// <param name="ui"></param>

/// <param name="property"></param>

/// <param name="value"></param>

private static void SetValue(System.Web.UI.Control ui, PropertyInfo property, object value)

{

    try

    {

        object convertedValue = Convert.ChangeType(value, property.PropertyType);

        property.SetValue(ui, convertedValue, null);

    }

    catch

    {

        // Log the error and continue gracefully

        // Allow business logic to care about the real value.

        // The job here is to make the best possible effort to

        // set values from the QueryString.

        return;

    }

}


The the SetField method attempts to convert the value to the property’s data type.  There’s a strong chance that this conversion can fail.  While this is probably the most arguable aspect of the whole endeavor, I think that validation is not necessary at this point, and would be better suited for later in the life cycle of the page.  The main goal of this code is to set properties on the page to values in the QueryString.  It seems a little silly to absorb an exception, but it was meant to keep the attribute from getting in your way.

Conclusion

The barrier to entry is really small for making this feature completely streamlined.  I created a new class to derive my Pages from, called PageBase (creative, right?).  It descends from System.Web.UI.Page, and overrides OnPreInit.

//--------------------------------------------------------------------------

/// <summary>

///        Some Page Base Class

/// </summary>

public class PageBase : Page

{

    protected override void OnPreInit(EventArgs e)

    {

        base.OnPreInit(e);

        this.ProcessAttributes();

    }

}


By calling ProcessAttributes in OnPreInit, derived classes get this functionality almost completely for free.  Even if you find a need to override OnPreInit in a descending class, base.OnPreInit(e) will keep that going.  The nice part about all of this is how it finally works:

[QueryStringValue]

protected string Value1 { get; set; }

 

[QueryStringValue]

protected string Value2 { get; set; }

 

[QueryStringValue("ThirdValue")]

public string Value3 { get; set; }

 

[QueryStringValue(new[] { "FourthValue", "Value4" })]

public string Value4 { get; set; }

 

[QueryStringValue]

public int Value5 { get; set; }

 

//-------------------------------------------------------------------------

/// <summary>

///        Page Load Event

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

protected void Page_Load(object sender, EventArgs e)

{

    Label1.Text = string.Format("Value1: [{0}], Value2: [{1}], Value3: [{2}], Value4: [{3}], Value5: [{4}]", Value1, Value2, Value3, Value4, Value5);

}


Using this approach to QueryStrings greatly reduces the amount of code you need to write each time a property needs to access the QueryString.  It’s robust because it supports multiple keys, and even works against public and protected properties.  It does its best to stay out of your way while you’re coding, and hopefully shaves off a few keystrokes here and there.

Going The Extra Mile

Craig pointed out that there are other possible uses for this approach, like Form Elements, Cache and Session.  I have looked at trying this out, but found it to be much more difficult.  Things get hairy when you need to save values.  I played with the idea of a [SessionValue] attribute, and even had some success.  I could provide an identical experience to the [QueryStringValue] attribute, where values are automatically assigned.  The problem is that Session variables often change over the course of a request.  If you’re using auto properties, the value just sits there.  That’s where I came up with a PersistableValueAttribute, deriving from AutoValueAttribute.  It added to the AutoValueAttribute functionality, because it provided a Persist method for saving off the value.  I tried to use this in the OnPreRenderComplete event, and it worked.  So it worked, what’s the problem?

Sometimes it is unavoidable, but you just have to tuck your ego in your back pocket and do what you have to do.  The situation I’m talking about is when the request comes in, you change a session variable, more code runs, and then other code uses the same session variable (knowing that it was changed) during the same request.  In the effort I described in the previous paragraph the [SessionValue] property isn’t persisted until the very last second.  That creates a problem when another code depends on that value before it’s persisted.  Oops.  Added complexity isn’t very fun.  I haven’t given up on the thought, but it’s a problem for another day.

Saturday, December 05, 2009 11:12:13 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] -
ASP.NET | Attributes | AutoValueAttribute | C# | Craig Shoemaker | QueryString
OpenID
Please login with either your OpenID above, or your details below.
Name
E-mail
(will show your gravatar icon)
Home page

Comment (Some html is allowed: a@href@title, b, blockquote@cite, i, strike, strong, sub, sup, u) where the @ means "attribute." For example, you can use <a href="" title=""> or <blockquote cite="Scott">.  

Enter the code shown (prevents robots):

Live Comment Preview

John Nelson

mugshot I am a passionate C# Developer working in ASP.NET on an e-commerce solution for ticketing software. I work across all of the application layers, including server side functionality, and client side programming with jQuery and MS Ajax. Although my full time job is in WebForms, I spend many of my off hours working with MVC. I am especially interested in productivity and good programming practices.

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2010
John Nelson
Statistics
Total Posts: 25
This Year: 3
This Month: 1
This Week: 0
Comments: 1