Click here to Skip to main content
Click here to Skip to main content
Articles » Web Development » ASP.NET » General » Downloads
 
Add your own
alternative version

Declarative QueryString Parameter Binding

, 2 Mar 2004
Describes using reflection to automatically populate member parameters from the Form and Querystring.
using System;
using System.ComponentModel;
using System.Reflection;

namespace piers7.Web.Controls
{
	/// <summary>
	/// Marks a field or property as being bound to a specific parameter present in the
	/// <see cref="System.Web.HttpRequest"/>. This attribute is normally only
	/// applied to subclasses of <see cref="System.Web.UI.Page"/>
	/// </summary>
	/// <example>
	/// Here a simple page class marks field with the attribute, and then
	/// calls the static WebParameterAttribute.SetValues() method to
	/// automatically load the fields with value from Request.Form or Request.QueryString
	/// (depending on what was used to submit the form). Note that since
	/// parameter binding in this example is done both on first-request
	/// and on postback, this page must always be either linked to supplying
	/// data in the querystring, or cross-posted to with the data in the Form.
	/// <code><![CDATA[
	/// public class BoundParameterDemo : System.Web.UI.Page{
	///		[WebParameter()]
	///		protected string FirstName;
	///
	///		[WebParameter("Last_Name")]
	///		protected string LastName;
	///
	///		[WebParameter(IsRequired=true)]
	///		protected int CustomerID;
	///
	///		private void Page_Load(object sender, System.EventArgs e) {
	///			WebParameterAttribute.SetValues(this, Request);
	///		}
	///	}
	/// ]]>
	/// </code>
	/// </example>
	[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple=false, Inherited=true)]
	public class WebParameterAttribute : Attribute
	{
		#region Declarations
		string _default;
		string _name;
		bool _isRequired;
		bool _isDefaultForInvalid;
		#endregion

		#region Constructors
		/// <summary>
		/// Creates a new WebParameterAttribute to load a field from an identically-named
		/// parameter in the Form/QueryString collection, if it exists.
		/// The parameter has no default value, and is not required
		/// </summary>
		public WebParameterAttribute()
		{
		}

		/// <summary>
		/// Creates a new WebParameterAttribute to load a field from the given parameter name
		/// The parameter has no default value, and is not required
		/// </summary>
		/// <param name="paramName">The key of a parameter in the Form or QueryString collections</param>
		public WebParameterAttribute(string paramName)
		{
			_name		=paramName;
		}
		#endregion

		/// <summary>
		/// The name (key) of the parameter being bound against in the Request
		/// </summary>
		public string ParameterName
		{
			get{	return _name;	}
			set{	_name=value;	}
		}
		/// <summary>
		/// An optional default value to use if the parameter doesn't exist
		/// in the current Request, or null to clear
		/// </summary>
		/// <remarks>Whilst this is a bit unneccesary for a field, its
		/// handy for properties - can save all that <code>if(ViewState["x"]==null)</code>
		/// stuff...</remarks>
		public string DefaultValue
		{
			get{	return _default	;	}
			set{	_default=value	;	}
		}
		/// <summary>
		/// Whether the absence of the parameter, along with the absence
		/// of a default, causes an error, rather than the default
		/// behaviour which is that the field will just be skipped.
		/// The default is false.
		/// </summary>
		public bool IsRequired
		{
			get{	return _isRequired;	}
			set{	_isRequired=value;	}
		}
		/// <summary>
		/// Whether the default value can be used if the value passed to
		/// the page is invalid in some way (rejected by the type converter,
		/// or causes an error on the field/property set).
		/// The default is false.
		/// </summary>
		public bool IsDefaultUsedForInvalid
		{
			get{	return _isDefaultForInvalid;	}
			set{	_isDefaultForInvalid=value;		}
		}

		/// <summary>
		/// Retrieves an item either from the Query or POST collections, depending on the
		/// mode of the request, or performs custom retrieval in derived classes
		/// </summary>
		/// <remarks>Typically a subclass would overide this to bind a parameter
		/// specifically to - say - Request.Form, or even an item in the Cookies
		/// collection.
		/// The parameter name has to be passed since some attributes have
		/// no ParameterName, marking them as using the same name as
		/// the member (which is unknown to the attribute)</remarks>
		protected virtual string GetValue(string paramName, System.Web.HttpRequest request)
		{
			if (request.HttpMethod.ToLower()=="post")
				return request.Form[paramName];
			else
				return request.QueryString[paramName];
		}

		/// <summary>
		/// Sets public properties and fields on <c>target</c> that are marked with
		/// <see cref="WebParameterAttribute"/> to the corresponding values retrieved from
		/// <c>request</c>, or a default value as set on the attribute
		/// </summary>
		/// <param name="target">The object (typically a <see cref="System.Web.UI.Page"/>) being bound</param>
		/// <param name="request">The <see cref="System.Web.HttpRequest"/> to load the data from.
		/// The attribute determines whether data is loaded from request.Form, request.QueryString
		/// or other parts of request</param>
		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);
		}

		/// <summary>
		/// Examines a single <c>member</c> (a property or field) for <see cref="WebParameterAttribute"/>.
		/// If so marked then the member is set on <c>target</c> with the relevant value
		/// retrieved from <c>request</c>, or the default value provided in the attribute
		/// </summary>
		/// <param name="target">The object (typically a <see cref="System.Web.UI.Page"/>) being bound</param>
		/// <param name="request">The <see cref="System.Web.HttpRequest"/> to load the data from.
		/// The attribute determines whether data is loaded from request.Form, request.QueryString
		/// or other parts of request</param>
		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)
				{
					// Just make sure we're not going after an indexed property
					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");
					}

					// There should only be one WebParameterAttribute (it's a single-use attribute)
					attrib		=attribs[0];
					paramName	=(attrib.ParameterName!=null) ? attrib.ParameterName : member.Name;
					paramValue	=attrib.GetValue(paramName, request);

					// Handle default value assignment, if required
					usingDefault	=false;
					if (paramValue==null)
					{
						if (attrib.DefaultValue!=null)
						{
							paramValue	=attrib.DefaultValue;
							usingDefault	=true;
						}
						else if (!attrib.IsRequired)
							return;	// Just skip the member
						else
							throw new ApplicationException(String.Format("Missing required parameter '{0}'", paramName));
					}

					// Now assign the loaded value onto the member, using the relevant type converter
					// Have to perform the assignment slightly differently for fields and properties
					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
					{
						// We catch errors both from the type converter
						// and from any problems in setting the field/property
						// (eg property-set rules, security, readonly properties)

						// If we're not already using the default, but there
						// is one, and we're allowed to use it for invalid data, give
						// it a go, otherwise just propagate the error

						if (!usingDefault && attrib.IsDefaultUsedForInvalid && attrib.DefaultValue!=null)
						{
							typedValue	=converter.ConvertFrom(attrib.DefaultValue);
							SetMemberValue(member, target, typedValue);
						}
						else
							throw;
					}
				}
			}
			catch(Exception err)
			{
				throw new ApplicationException("Property/field {0} could not be set from request - " + err.Message, err);
			}
		}

		/// <summary>
		/// Sets <c>member</c> on <c>target</c> to <c>value</c>. <c>member</c>
		/// may be a field or a property
		/// </summary>
		private static void SetMemberValue(MemberInfo member, object target, object value)
		{
			switch(member.MemberType)
			{
				case MemberTypes.Field:
					((FieldInfo)member).SetValue(target, value);
					break;

				case MemberTypes.Property:
					// We've already ensured this isn't an indexed property
					((PropertyInfo)member).SetValue(target, value, new Object[0]);
					break;
			}
		}

		/// <summary>
		/// Retrieves the <see cref="Type"/> that <c>member</c> represents
		/// (that is to say the type of the member on the object, rather
		/// than the subtype of MemberInfo). <c>member</c> may be a
		/// <see cref="FieldInfo"/> or a <see cref="PropertyInfo"/>
		/// </summary>
		private static Type GetMemberUnderlyingType(MemberInfo member)
		{
			switch(member.MemberType)
			{
				case MemberTypes.Field:
					return ((FieldInfo)member).FieldType;

				case MemberTypes.Property:
					return ((PropertyInfo)member).PropertyType;

				default:
					throw new ArgumentException("Expected a FieldInfo or PropertyInfo", "member");
			}
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

piers7
Web Developer
Australia Australia
There's some kinda mutex between money and the time to enjoy it, and it's called work.

| Advertise | Privacy | Mobile
Web02 | 2.8.140721.1 | Last Updated 3 Mar 2004
Article Copyright 2004 by piers7
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid