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

Reading Query String Parameters using Custom Attributes and Reflection

, 21 Oct 2006
Rate this:
Please Sign up or sign in to vote.
Using Custom Attributes and Reflection techniques in VB.NET to read query string parameters.

Introduction

I always like to keep a balance between easy to read code and reducing the amount of repetitive work I have to do to achieve something fast. If it’s too complicated to read and understand in a few minutes, it's going to make support difficult.

This article describes a technique I use to achieve these two goals when it comes to dealing with Query Strings in ASP.NET. It’s an easy to plug in helper class that’s easy to follow with little setup/method calls required. I’m sure we’ve all written code, particularly in our page load event such as:

Dim product As String = Page.Request.QueryString("Product")

It’s OK for just one or two, but it gets repetitive, everyone knows what it does, and it’s boring code.

I’m going to show you a technique for reducing this “boring” code and getting it out of that page load event so you can concentrate on the important stuff.

I use two of my favourite elements of .NET, custom attributes and reflection. I don’t use them that often but when I find need for them, I find them both easy and fascinating. I’ll be describing exactly how I use them, and hopefully you’ll see the benefits of using them in lots of other areas as well.

Using the code

I like to keep my code very portable. If there's too many dependencies, then you’ll forget to include one, or give up trying to share useful code libraries. What I present is a single VB file that contains two classes. The QueryStringParameterAttribute custom attribute class used for decorating fields within your page with hints on how they should be processed, and the QueryStringAttributes class which contains a single shared method to actually do the processing.

Add the file to your project, and in your page where you want to handle query strings, do the following:

For each field that you have in your query string, define a public variable with either of the following methods:

<QueryStringParameter()> Public ProductID As String = Nothing
<QueryStringParameter(“V”)> Public ViewType As Integer

Use the first syntax if the field name exactly matches the query string attribute, the second if you want to alias it (in this case, the query string parameter is V).

Then in your page load event, just call this single line:

QueryStringAttributes.Process(Me)

After this method call, your fields will be populated with their appropriate query string values if they existed, otherwise they’ll have their default values.

Just one line of code to retrieve as many query string values as you have, not cluttering up your page load event, and no writing boring code.

How to create a Custom Attribute

Creating a custom attribute is just the same as creating a normal class, just with a bit of extra decoration to expose it as a custom attribute. With this decoration, Intellisense picks up the usage of your attribute and adds it in to the standard Intellisense menus automatically.

Here’s my custom attribute class:

<AttributeUsage(AttributeTargets.Field)> _
Public Class QueryStringParameterAttribute
    Inherits System.Attribute

    Friend QueryStringParameter As String = Nothing

    Sub New()
        'Default - takes the name of the field as the query string parameter
    End Sub

    Sub New(ByVal queryStringParameter As String)
        'Overrides the default
        'specifies a different query string parameter
        'to populate the field with
        Me.QueryStringParameter = queryStringParameter
    End Sub
End Class

It’s just a normal class with two constructors, allowing for the query string parameter to be specified manually or picked up from the field name. The only difference is that the class has to inherit from System.Attribute and the AttributeUsage attribute on the class. This takes several parameters to specify how the attribute can be used. Here, I’m only allowing fields to have this attribute assigned to them but you can specify methods, properties, whole classes, or a combination of them.

Notice that I end my class name in the word Attribute. Under .NET 1.1, this was mandatory and your class would not be recognised as an attribute if you didn’t do so. Under .NET 2.0, this seems to be optional, if you do suffix the word attribute then Intellisense removes it. I keep to the old .NET 1.1 standard as this way the class is reusable over different versions and also I think helps describe the class better anyway.

Once you have your class defined, you should be able to see it through Intellisense in your project code by just using an angle bracket on a field.

Using Reflection to query your code

This I find is the most interesting area, using code to examine your own code at runtime!

In my code file, this happens in one shared Sub called Process. It takes a single parameter pointing to the page to process. I’ve explicitly typed this to only accept system.web.UI.page objects as that’s all we’re dealing with here. If your own custom attributes/reflection processor is more generic, then just make it an object. Using reflection, you can work out the type of object very easily.

I’ve commented this area of code very extensively, it’s quite a difficult subject and requires a bit of abstract thinking since the things you are dealing with only exist at runtime.

The lines below are what set us up for reflection. It gets the type of our custom attribute and page, allowing us to use reflection to determine fields, properties, methods etc., in this type and compare their attributes against our custom definition.

Dim attType As Type = GetType(QueryStringParameterAttribute)
Dim classType As Type = PageToProcess.GetType

Since we’re only concerned about fields, we don’t have to worry about enumerating all the types in the class, instead we can jump straight in and just get a list of fields. The flags variable defines the type of fields we are interested in, in this case both public and private. This is done with the following lines:

'Setup the scope were looking for, in this case public or private
Dim flags As Reflection.BindingFlags = Reflection.BindingFlags.NonPublic Or _
Reflection.BindingFlags.Public Or _
Reflection.BindingFlags.Instance

'Get all the fields of the class (using the bindings above to specify scope)
'iterate through them and see if any contain our custom attribute
Dim fields() As Reflection.FieldInfo = classType.GetFields(flags)

Once we have that array, we can quite simply iterate through it, examing each field and seeing if it has our custom attribute defined. This is done with the following code segment:

Dim attributes() As Object

'Retrieve all QueryStringParameter attributes for the field
attributes = field.GetCustomAttributes(attType, False)

'Check whether the array contains at least one element 
'of our custom attribute class
If attributes.Length > 0 Then
  Dim att As QueryStringParameterAttribute
  att = DirectCast(attributes(0), QueryStringParameterAttribute)
…
End If

Here, we’re getting all custom attributes of our specific type (the attType variable which we earlier set to be the type of our custom attribute). Since custom attributes can optionally be used multiple times within the same field, we retrieve these into an array. Since we know that when we setup our custom attribute we only specified single use, we are only interested if the array holds at least one of these, and only reference the first one directly, casting it for a generic custom object into our specific custom attribute class to allow us to retrieve its fields.

Next, we examine the query string parameter name field;

Dim queryStringParameterName As String

If att.QueryStringParameter Is Nothing Then
  queryStringParameterName = field.Name
Else
  queryStringParameterName = att.QueryStringParameter
End If

If it’s empty (using the basic new constructor), then we use the field name of the class we are currently reflecting, otherwise we use the parameter name specified in the constructor.

We’re on the home straight now, we have retrieved all the information we need to get the parameter and set it to a field. We get the parameter as follows:

Dim itemStringValue As String = Nothing
itemStringValue = pageToProcess.Request.QueryString(queryStringParameterName)

This is what we all know and hate writing, that boring request.querystring code. But hopefully the last time!

Finally, now we have a string representation of the parameter, we have to dynamically cast it to the destination field type. First, we get the field type code:

Dim targetTypeCode As System.TypeCode
targetTypeCode = Type.GetTypeCode(field.FieldType)

Then, we convert it to our field's type:

Dim itemConvertedValue As Object = _
       Convert.ChangeType(itemStringValue, targetTypeCode)

And finally, we set our target field to the correctly typed value:

field.SetValue(PageToProcess, itemConvertedValue)

That’s it! We just wrap the above code in a For Each on the fields array and we process all custom attributes within our class in one method call.

Conclusion

Although query string retrieval is a simple piece of code to automate, I hope this has served as a good introduction to custom attributes and reflection. Using the basic techniques laid out here, you can expand this for more complex operations.

With another simple method practically reversing what we have here, we can write to fields. With those two in combination, we could achieve things like automated get/set of viewstate.

Using class attributes as well as field attributes, we could specify stored procedures to call, mapping fields to parameters of those procedures.

It’s just a case of determining if the effort in creating a custom attribute and the reflection code to query it outweighs the amount of effort to write the code, and write it accurately, over and over.

This is my first article posted to CodeProject or anywhere, so I hope I’ve managed to deliver my point clearly and you find these techniques useful. If you come up with other ways of using them, please let me know, or better still write an article here yourself.

History

  • 22nd October 2006 - Initial article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

CodeChimp
Web Developer
United Kingdom United Kingdom
A general purpose analyst programmer working in a small development team for a company in the city.
 
VB has always been my passion and I code both Win forms and web based systems for both work and fun.

Comments and Discussions

 
GeneralNice implementation - just take care of the performance! PinmemberCorneliu Tusnea23-Oct-06 20:04 
GeneralRe: Nice implementation - just take care of the performance! PinmemberCodeChimp23-Oct-06 23:49 
GeneralRe: Nice implementation - just take care of the performance! PinmemberHal Angseesing30-Oct-06 23:21 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140721.1 | Last Updated 22 Oct 2006
Article Copyright 2006 by CodeChimp
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid