Reflecting Data to .NET Classes: Part I - From HTML Forms

Reflection allows us to examine internal details of assemblies and classes at runtime (programmatically). Using reflection, you can find out everything about a class, as well as dynamic object creations and method invocations. As such, with the help of Reflection, we can carry out some automated data mappings. That means no more manual line-by-line mappings, taking a value from one place, and assign to another etc.

This article is the first part of a four parts series. In first part of this series, Ill discuss Reflection from HTML Forms. In other parts of this series, Ill discuss Reflection from XML Documents, from Windows Forms, and from Database Tables respectively.

You probably would wonder what kind or title is this? Reflection Data?? What does that mean? Youve header reading and writing data but probably not heard of Reflection data? How do you reflect data? Well, this article is making use of the Reflection feature provided by the classes in the System.Reflection namespace. OK, that should explain the title of this article. Now lets see how Reflection can make it happen.

The example was written in C# in Notepad, and compiled with the .NET Framework SDK C# compiler version 7.00.9466 (the final release). While the syntax will be different in other .NET languages (ex: VB.NET), but the mapping mechanism will still be the same.

First, lets look at the HTML form:

<%@ page language = "c#" %>
<%
if ( Request.RequestType == "POST" ) {
BusinessEntity.member m = new BusinessEntity.member();
if ( DataReflector.HTMLForm.fillProperties(m, "post") ) {
if ( m.login() )
Response.Write( m + "<br>status: Logged In." );
else
Response.Write( "status: Invalid Login." );
Response.End();
} else
Response.Write("error<p>");
}
%>
<html>
<
head>
</
head>
<
body>
please login:
<form name="login" method="post" action="login.aspx">
username: <input type="text" name="username" value=""><br>
password: <input type="text" name="password" value=""><br>
<
input type="submit" value="login!">
</
form>
</
body>
</
html>

Ok, its not the best style you can write ASP.NET pages in. But its not our focus here. When you post this HTML form, you first create an object of type BusinessEntity.member. Then you pass the object to the DataReflector.HTMLForm.fillProperties() method. Both of the BusinessEntity.member and DataReflect.HTMLForm classes are located in the bin folder. This method, as its name states, fills the properties of an object for us. We just have to pass the object, and a string indicating the form method, which is post in our case. When the method returns, assume everything goes well, the object will have its properties filled with the data from the HTML form fields. Note that the mapping relies on the identical naming between the class properties and the form fields. At this point, the object will have enough data to work with. It then validates the login by calling login() method. And depending on the value the method returns, we display different information.

Lets look at how this BusinessEntity.member class:

using System;
namespace BusinessEntity
{
public class member
{
private int _memberID;
private string _username;
private string _password;
public member() {}
public int memberID
{
get { return memberID; }
set { _memberID = value; }
}
public string username
{
get { return _username; }
set { _username = value; }
}
public string password
{
get { return _password; }
set { _password = value; }
}
public bool login()
{
if ( _username == null || _password == null )
return false;
if ( _username.Length != _password.Length )
return false;
_memberID = DateTime.Now.Millisecond;
return true;
}
public override string ToString()
{
return ( "member id = " + _memberID + ", username = "+ _username + ", password = " + _password );
}
}
}

There are 3 properties: username, password and memberID. The property username and password will be reflected from the forms, while the memberID is generated by the login() method. The logic of the login() method is simple. As long as the length of the username and password are the same, then we decide its valid, and assign a memberID to the object base on the current time. The override ToString() method, which simply returns a string containing the values of the 3 properties.

Make sure the SDK C# compiler (csc.exe) is in the path environment variable (right click My Computer, select properties, Advanced tab, Environment Variables, System variables path). By default, this csc.exe is located in a folder like this: C:\WINNT\Microsoft.NET\Framework\v1.0.3705. Compile this class: csc /t:library member.cs, and make sure you have the compiled .DLL file in the bin folder under the folder where you have the login.aspx. Detailed instructions can be found in the readme.txt of the zip file download.

Now, lets see the real stuff:

using System;
using System.Web;
using System.Reflection;
namespace DataReflector
{
public class HTMLForm
{
/// <summary>
/// This method will get the data from the HTML form via the method specified in the 2nd parameter,
/// and assign them to the properties of the object passed from the first parameter.
/// </summary>
/// <param name="obj">The object, whose properties are to be filled</param>
/// <param name="method">The request method. "get" or "post"</param>
/// <returns>true if no error, false otherwise.</returns>
public static bool fillProperties(object obj, string method)
{
Type t =
null;
PropertyInfo[] properties =
null;
object currentValue = null;
HttpRequest req;
HttpContext ctx;
try
{
// get the current HTTP Context the object is running in
ctx = HttpContext.Current;
if ( ctx == null )
throw new Exception("Current HTTP Context is null!");
else
{
// then get the current HTTP Request from the HTTP Context
req = ctx.Request;
if ( req == null )
throw new Exception("Current Request Object is null!");
}
method = method.ToLower();
// get the Type object of the object
t = obj.GetType();
// get all the properties of the Type
properties = t.GetProperties();
// taking a property at a time ...
foreach (PropertyInfo proc in properties)
{
// if it's writable,
if ( proc.CanWrite )
{
try
{
// then match a value from the QS/form
currentValue = getObjectFromParam(req, proc.Name, proc.PropertyType.ToString(), method);
// if there's a value returned,
if ( currentValue != null )
// then assign the value to the property
t.InvokeMember(
proc.Name,
BindingFlags.Default | BindingFlags.SetProperty,
null,
obj,
new object [] { currentValue }
);
}
catch(Exception e)
{
throw e;
}
}
}
return true;
}
catch (Exception ex)
{
throw ex;
}
}
public static object getObjectFromParam(HttpRequest req, string paramName, string paramType, string method)
{
try
{
string paramValue;
method = method.ToLower();
paramType = paramType.Replace("System.", "");
if ( method == "post" )
paramValue = req.Form.Get(paramName);
else if ( method == "get" )
paramValue = req.QueryString.Get(paramName);
else
return null; // the request method is not supported
// if the param doesn't even exist
if ( paramValue == null )
return null;
if ( paramType == "Byte" ) return Byte.Parse( paramValue.Trim() );
if ( paramType == "Char" ) return Char.Parse( paramValue.Trim() );
if ( paramType == "Decimal" ) return Decimal.Parse( paramValue.Trim() );
if ( paramType == "Double" ) return Double.Parse( paramValue.Trim() );
if ( paramType == "Int16" ) return Int16.Parse( paramValue.Trim() );
if ( paramType == "Int32" ) return Int32.Parse( paramValue.Trim() );
if ( paramType == "Int64" ) return Int64.Parse( paramValue.Trim() );
if ( paramType == "SByte" ) return SByte.Parse( paramValue.Trim() );
if ( paramType == "Single" ) return Single.Parse( paramValue.Trim() );
if ( paramType == "UInt16" ) return UInt16.Parse( paramValue.Trim() );
if ( paramType == "UInt32" ) return UInt32.Parse( paramValue.Trim() );
if ( paramType == "UInt64" ) return UInt64.Parse( paramValue.Trim() );
if ( paramType == "DateTime" ) return DateTime.Parse( paramValue.Trim() );
if ( paramType == "String" ) return paramValue;
if ( paramType == "Boolean" )
{
switch( paramValue.Trim().ToLower() )
{
case "+":
case "1":
case "ok":
case "right":
case "on":
case "true":
case "t":
case "yes":
case "y":
return true;
default:
return false;
}
}
return null; // if paramType is not a primative type
}
catch
{
return null; // paramType might be a primitive type, but there's an error parsing the value.
} // ex: paramValue = "" for numeric types or null
}
}
}

So thats how we did the mapping and reflecting. This method first gets the HTTP Request object, which has the data thats posted from the form. The static property Current of the HttpContext class will return the HTTP Context object that the object is running in. This happens even if we dont pass a reference to the HttpContext object nor the HttpRequest object, from the ASP.NET page! It then creates a Type object from the object we pass to it. The Type object knows everything about the object, including its properties. So we get an array of its PropertyInfo objects, which all contain the kind of information like the name, type, accessibility etc. Then we loop through the array, taking one property at a time. First see if its writable, if it is, then we try to get a value from the form fields that has the same name as the property by calling the getObjectFromParam() method. If theres a value returned (not null), we call the InvokeMember() method to assign the value to the property. The System.Type.InvokeMember() is the heart of everything here. Its an overloaded method with 3 signatures. More information about it can be found in the MSDN Library at: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/
frlrfSystemTypeClassInvokeMemberTopic.asp?frame=true. Compile this class: csc /t:library HTMLForm.cs, and make sure you have the compiled .DLL file in the bin folder understand the folder where you put the login.aspx. Again, detailed instruction can be found in readme.txt of the zip download.


DataReflector in Action

This is a simple demonstration of the example. It shows that the DataReflector does reflect data to the class properties from the html form fields, for the login() to do its work.

First, try logging in with tin and lam for the username and password.

ReflectDataInNetP1Img1.jpg

From whats shown on the screen, we can tell that the login is valid, because both tin and lam are 3 characters long.

ReflectDataInNetP1Img2.jpg

Now, theres just an extra character in the username, an x.

ReflectDataInNetP1Img3.jpg

Ah, not good.

ReflectDataInNetP1Img4.jpg

Conclusion

By now, you should understand how we reflect data. Our example only made use of 2 form fields. But As long as you properly name the primitive type properties and the form fields, the DataReflector.HTMLForm.fillProperties() is good for even a hundred of them! And as I mentioned earlier, this is only the first in the series of Reflecting Data to .NET Classes. The remaining 3 parts will demonstrate how you can apply the same technique in mapping data from XML documents, Windows Forms, as well as Database Tables.