Introduction
This article discusses a strategy for automatic population of instance fields and properties on an ASP.NET page from parameters supplied via properties of the Request
object (.QueryString
, .Form
etc...).
The Problem
If you have parameterized ASP.NET pages - i.e. pages that modify their output on the basis of some input from the GET (Request.QueryString
) or POST (Request.Form
) parts of the request - then you probably spend a lot of time writing parameter retrieval code, which probably started out like this:
protected string FirstName{
get{ return Request.QueryString["FirstName"]; }
}
...but then you have to deal with the potential of FirstName
being null
throughout the rest of your code, so you probably supply a default empty value, by rewriting like this (Note: handy IsNull
method, we'll be using throughout the code):
protected static string IsNull(string test, string defaultValue){
return test==null ? defaultValue : test;
}
protected string FirstName_NullSafe{
get{ return IsNull(Request.QueryString["FirstName"],""); }
}
This approach works fine for non-string types too, you've just got to add your Convert.ToXXX()
statement into your property, and decide what to do when the parameter is null
- supply a default, or throw an exception:
protected int CustomerID{
get{
object o=Request.QueryString["CustomerID"];
if (o==null)
throw new
ApplicationException("Customer ID is required to be passed");
else
try{
return Convert.ToInt32(o,10);
}catch(Exception err){
throw new ApplicationException("Invalid CustomerID", err);
}
}
}
}
Yuk. What started out as a simple accessor is starting to grow a bit. Also that code's running every time we access CustomerID
- so that's unnecessary overhead - and it doesn't run till you first access it, so you get halfway through your (expensive?) process before that exception gets thrown.
It's probably about this point that we refactor the whole mess, and do all this work upfront in the Page_Load
(or the end of OnInit
):
private void Page_Load(object sender, System.EventArgs e) {
string o;
FirstName2 =IsNull(Request.QueryString["FirstName"], "");
o =Request.QueryString["CustomerID"];
if (o==null)
throw new
ApplicationException("Customer ID is required to be passed");
else
try{
CustomerID2 = Convert.ToInt32(o,10);
}catch(Exception err){
throw new ApplicationException("Invalid CustomerID", err);
}
o =Request.QueryString["Gender"];
if (o==null)
throw new ApplicationException("Gender is required");
else
try{
Gender =(Gender)Enum.Parse(typeof(Gender), o, true);
}catch(Exception err){
throw new ApplicationException("Invalid Gender", err);
}
}
Now you've only got to do this a couple of times, and there's a clear pattern emerging. Whether you're populating fields or properties (i.e. ViewState wrappers), there's a couple of standard actions going on:
- Retrieving a value from
Request.QueryString
(or Request.Form
), based on a key (potentially the same as the field name)
- Throwing an appropriate exception if the value wasn't supplied, or supplying a default
- Converting the value into the appropriate type for the field / property
- Throwing an appropriate exception if the value doesn't convert properly (or again, supplying a default)
Now, I wouldn't be a proper coder if I wasn't lazy, and when it comes to this kind of boring repetitive stuff, I'm well lazy. So what's the alternative?
The Solution: Declarative Parameter Binding
Well, one solution would be to just 'mark up' the fields and properties we want loaded with appropriate metadata (specifying defaults, what key to bind against and in which collection) and just let some library code perform the actual work.
Kind of like this:
[WebParameter()]
protected string FirstName;
[WebParameter("Last_Name")]
protected string LastName;
[WebParameter(IsRequired=true)]
protected int CustomerID;
The optional constructor parameter just supplies the key to find the parameter in the Request.QueryString
collection, and the IsRequired
property toggles whether omissions are an exception condition, rather than a skip condition.
All of a sudden, a great mass of code has condensed into a few attributes, which makes it easier to see at-a-glance what's going on, and simpler to maintain. All we need to do now is implement all that common logic we just trimmed out in some kind of helper class. This would use reflection to examine the Page
class, find all the properties (or fields) we marked with the attributes, and make the relevant assignments from QueryString
automatically.
(If you've never used reflection in .NET before, here's what you need to know to follow the code:
- At runtime, every object has a
Type
, from which we can get a representation of the fields and properties exposed by that Type
- These representations -
FieldInfo
and PropertyInfo
- share a common base class - MemberInfo
MemberInfo
subclasses provide a way of performing 'late bound' interactions with object members, rather than having to pre-specify what's going to be performed at compile-time. So a PropertyInfo
allows you to do get
/set
'ing of that property on an object, without having to hard-code object1.property1=myValue
into your source.
There're lots of tutorials about if you're interested.)
The first step is to do exactly that, just loop through the properties and members on the class (here assigned to target
), and pass each member (along with the current HttpRequest
(request
)) down to another method that's actually going to do the work.
public static void SetValues(object target, System.Web.HttpRequest request)
{
System.Type type =target.GetType();
FieldInfo[] fields =
type.GetFields(BindingFlags.Instance | BindingFlags.Public);
PropertyInfo[] properties =
type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
MemberInfo[] members =new MemberInfo[fields.Length + properties.Length];
fields.CopyTo(members, 0);
properties.CopyTo(members, fields.Length);
for(int f=0;f<members.Length;f++)
SetValue(members[f], target, request);
}
The only slightly confusing bit of this step is that we merge the properties
and fields
arrays into one members
array, which just means we only have to do one loop round. All the real work gets done in SetValue
(singular!), which we'll discuss next. Now there's a fair chunk of code here, but what it does is fairly straightforward when broken down.
Firstly, we exclude members that aren't marked with WebParameterAttribute
(the outermost if
), and because there's no simple way of dealing with indexed parameters, we throw an error if they've been marked (you might choose to just skip the member silently):
public static void SetValue(MemberInfo member,
object target, System.Web.HttpRequest request)
{
WebParameterAttribute[] attribs;
WebParameterAttribute attrib;
TypeConverter converter;
string paramValue;
string paramName;
object typedValue;
bool usingDefault;
try
{
attribs = (WebParameterAttribute[])
member.GetCustomAttributes(typeof(WebParameterAttribute), true);
if(attribs!=null && attribs.Length>0)
{
if (member.MemberType==MemberTypes.Property)
{
ParameterInfo[] ps =
((PropertyInfo)member).GetIndexParameters();
if (ps!=null && ps.Length>0)
throw new NotSupportedException("Cannot apply " +
"WebParameterAttribute to indexed property");
}
Now we get the various settings from the attribute, and the (string) parameter value itself.
attrib =attribs[0];
paramName =(attrib.ParameterName!=null) ?
attrib.ParameterName : member.Name;
paramValue =attrib.GetValue(paramName, request);
Note that it's the attribute itself which supplies the actual parameter value we're after, making its own determination on which part of the HttpRequest
to use. Delegating responsibility for non-core activities like this makes for a more flexible solution. In our case WebParameter
- the only attribute we've talked about so far - selects either Request.Form
or Request.QueryString
depending on the method which was used to submit the form. Alternatively other, specialized, subclasses like QueryParameter
and FormParameter
can perform binding to specific parts of the request, and others could be easily created for Request.Cookies
or Request.Session
as required.
If the attribute returns null
, it signifies that the parameter wasn't found in the Request
. Our options then are fairly straightforward:
- Use the attribute's
DefaultValue
, if supplied
- Throw an error if the attribute is marked
IsRequired
- Skip this member entirely by returning back to the calling method
usingDefault =false;
if (paramValue==null)
{
if (attrib.DefaultValue!=null)
{
paramValue =attrib.DefaultValue;
usingDefault =true;
}
else if (!attrib.IsRequired)
return;
else
throw new
ApplicationException(String.Format("Missing " +
"required parameter '{0}'", paramName));
}
Now (finally), we can actually take our string and assign it to the member. We've got a couple of helper methods here just to make the code simpler (I won't reproduce the full source code here, but it's all in the example files):
GetMemberUnderlyingType(...)
returns the Type
that a MemberInfo
represents, i.e. returns .FieldType
or .PropertyType
SetMemberValue(...)
wraps the code required to do reflective field / property assignment
I'm using a TypeConverter
to do the actual string conversion, because this delegates responsibility for the string parsing to the outside world (which is always a good idea). If you haven't come across them, the bottom line is that they're a completely extensible scheme to associate type conversion routines with the types they convert to/from. They're principally used in control development (that's how the VS.NET IDE's property browser works), but they're far too useful to only use in the designer and I've started using them all over.
converter =
TypeDescriptor.GetConverter(GetMemberUnderlyingType(member));
if (converter==null ||
!converter.CanConvertFrom(paramValue.GetType()))
throw new
ApplicationException(String.Format("Could not" +
" convert from {0}", paramValue.GetType()));
try
{
typedValue =converter.ConvertFrom(paramValue);
SetMemberValue(member, target, typedValue);
}
catch
{
if (!usingDefault && attrib.IsDefaultUsedForInvalid
&& attrib.DefaultValue!=null)
{
typedValue =converter.ConvertFrom(attrib.DefaultValue);
SetMemberValue(member, target, typedValue);
}
else
throw;
}
}
Finally, we pick up on any exceptions arising from anywhere in the handling for this field, and wrap them in a standard error message
}
catch(Exception err)
{
throw new ApplicationException("Property/field " +
"{0} could not be set from request - " + err.Message, err);
}
}
Phew.
Now the obvious place for all this code would be in a base Page
class, but I have a real issue with creating unnecessary Page
subclasses, so the 'engine' for my implementation is just a static method on the WebParameter
class itself. I felt a bit dirty putting quite so much code on an attribute class, but it's not the end of the world.
private void Page_Load(object sender, System.EventArgs e)
{
WebParameterAttribute.SetValues(this, Request);
}
Having to call it explicitly like this also saves on the overhead when you don't need it done, and means you get to choose when to bind: first time round; postback; every request - what you choose will depend on what your page does and whether you're binding to field or viewstate property accessors.
Full Example
Having imported the relevant namespaces (and assemblies) into a page, all that's actually required to do all this is the very briefest of code (this is from the included example):
public class WebParameterDemo : System.Web.UI.Page
{
[QueryParameter("txtFirstName")]
public string FirstName ="field default";
[QueryParameter("txtLastName", DefaultValue="attribute default")]
public string LastName{
get{ return (string)ViewState["LastName"]; }
set{ ViewState["LastName"]=value; }
}
[QueryParameter("txtCustomerID", DefaultValue="0")]
public int CustomerID;
private void Page_Load(object sender, System.EventArgs e)
{
WebParameterAttribute.SetValues(this, Request);
}
}
Benefits
- Clean, simple approach improves legibility and maintainability
- Centralized pattern means one less thing to test at a
Page
level
- Page's API is 'self documenting'
- For paper documentation purposes
- For other pages in the application that might want to construct requests for your page (provided they know what properties/fields they're trying to set)
- Automated testing (e.g.: construct requests with the intention of making the page fall over)
Note also that it doesn't just have to be the Page
class you bind to, any class can be 'bound', though it'd be rare for something other than a UserControl
to be bound to Request.Form
or Request.QueryString
directly (especially since you could provide a page-level property to indirect the binding).
Other parts of Request
you might want to bind to:
Session
ServerVariables
Headers
- the current
HttpContext.Items
Now, I was toying with using a similar approach to this for Console apps, but it turns out someone already did it (or something pretty similar), so if I've sold you on this, check out CommandLineOptions and ditch all that args[]
parsing nonsense. Thank you Gert.
Drawbacks
I like this approach a lot, it gives me more time for writing the complicated convoluted bits that get me out of bed in the morning. However it has one downside, and that's performance. Like anything based on reflection, its going to be slower than the equivalent direct call; I loop through a lot of unnecessary properties looking for those that are bound (and the Page
class is not exactly short on public properties); and I'm using TypeConverter
s to do my string-conversions, which is yet more reflection.
That being said, reflection's only a bit slower than normal. Sure you wouldn't use it in your 3D rendering routines, but compared to the cost of your database access, wire-latency and other issues inherent in a web app and it's nothing. All that ASP.NET data binding you're using - that's reflection based. Still, if you're desperate, you could probably speed it all up by:
- Restrict the
GetFields()
/ GetProperties()
calls in WebParameterAttribute.SetValues()
to only return members declared in your code behind (BindingFlags.DeclaredOnly
), rather than include all the base class members (NB: Since your code behind is itself sub-classed to build the final page, this means you'd have to pass its Type
into GetValues
as well).
- Provide an interface for retrieving a
MemberInfo[]
array of members to bind, rather than having to find them by reflection.
- See if using
TypeDescriptor.GetProperties()
with an attribute filter is any quicker than 'raw' reflection.
- Explicitly call
SetValue()
on a member-by-member basis in your page, rather than SetValues()
- Provide a couple of 'baked-in' type-conversion steps before defaulting to using the
TypeConverter
Finally, I should point out that in real life, I didn't hard-code the message into the exceptions like this - I stuck them in a resource file. Even though I don't intend globalizing my application, it makes it a lot easier for people re-using the assembly (including myself) to alter the messages to suit their needs without having to trawl through the source code.
History
2004-02-15 - First, much delayed, release.
Appendix 1 - Why not to subclass System.Web.UI.Page
In the 'normal' world of OO programming, people believe in building little helper objects that can be used / aggregated within other classes to perform specific bits of functionality. However, when it comes to ASP.NET, there's way too much temptation to write your handy bit of common functionality as a base page class, with the obvious drawback that to use it you've got to derive from it, which means you're not deriving from something else.
Additionally Page
is a pain to subclass from - you've got properties and methods coming out of your ears and you can't make anything abstract (or have any properties that return null
unless you attribute them appropriately) or the designer chokes on it. So your object hierarchy becomes a mess of virtual methods that throw exceptions along the lines of 'this method should have been overridden in a subclass'. Yuk. On top of all this, the designer slows to a crawl since it has to create all the stuff your page creates (this is especially slow if there's database access involved), and you might not get the .config file you expected for all the dependent assemblies.
My advice is to have a few base page classes, sure, but keep it simple, and aggregate as much as possible of the more complex stuff as helper objects (even if they have to get passed a reference to their containing Page
).