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:
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:
Public Class QueryStringParameterAttribute
Friend QueryStringParameter As String = Nothing
Sub New(ByVal queryStringParameter As String)
Me.QueryStringParameter = queryStringParameter
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
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:
Dim flags As Reflection.BindingFlags = Reflection.BindingFlags.NonPublic Or _
Reflection.BindingFlags.Public Or _
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
attributes = field.GetCustomAttributes(attType, False)
If attributes.Length > 0 Then
Dim att As QueryStringParameterAttribute
att = DirectCast(attributes(0), QueryStringParameterAttribute)
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
queryStringParameterName = att.QueryStringParameter
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 = _
And finally, we set our target field to the correctly typed value:
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.
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.
- 22nd October 2006 - Initial article.