This morning Julian Voelcker came to me with an interesting issue that I’ve looked into before but I’ve never really looked into a re-useable solution. Seeing as it’s fun Friday I thought why not ;)
The scenario: I would like to offer my users a custom mail merge facility where by they can insert values stored in the database such as their name. The selection of columns is unlikely to be changed and if it does then I’ll be the one to do it. There are about 20 fields to choose from.
Easy enough, in the past I’ve kept it to a minimum and then just done a simple find and replace on the body i.e.:
//Create a dataset and add some test columns
DataTable dt =
new DataTable();

dt.Columns.Add(
"Name");

dt.Columns.Add(
"Email");
Add some test data
#region Add some test data


DataRow dr = dt.NewRow();

dr[
"Name"] =
"Julian";

dr[
"Email"] =
"[email protected]";

dt.Rows.Add(dr);


dr = dt.NewRow();

dr[
"Name"] =
"Tim";

dr[
"Email"] =
"[email protected]";

dt.Rows.Add(dr);

#endregion
Create the example email body
Do the work
#region Do the work

//Loop through the rows
for (
int i =
0; i < dt.Rows.Count; i++)
{...}

{
//Get the data row for this instance
DataRow row = dt.Rows[i];

//Create a new body as this'll be updated for each user
string body =
String.Empty;

//Update the body
body = emailBody.Replace(
"##Name##", row[
"Name"]);

body = body.Replace(
"##Email##", row[
"Email"]);


litOutput.Text +=
String.Format(
"{0}<hr />", body);

}

#endregion
The issue I see with this however is (among others) having 20 fields is a lot to be doing with a find/replace statement as it wouldn’t be very elegant and a nightmare to manage. Sticking with this method of using a dataset I suggested we use a regular expression to match the field delimiters and do a replace that way:
//Create a dataset and add some test columns
DataTable dt =
new DataTable();

dt.Columns.Add(
"Name");

dt.Columns.Add(
"Email");
Add some test data
#region Add some test data


DataRow dr = dt.NewRow();

dr[
"Name"] =
"Julian";

dr[
"Email"] =
"[email protected]";

dt.Rows.Add(dr);


dr = dt.NewRow();

dr[
"Name"] =
"Tim";

dr[
"Email"] =
"[email protected]";

dt.Rows.Add(dr);

#endregion
Create the example email body
Do the work
#region Do the work

//Loop through the rows
for (
int i =
0; i < dt.Rows.Count; i++)
{...}

{
//Get the data row for this instance
DataRow row = dt.Rows[i];


MatchEvaluator replaceField =
delegate(Match m)
{...};

{
return row[m.Groups[
1].ToString()].ToString();

};

//Create a new body as this'll be updated for each user
string body =
String.Empty;
//Find the fields
Regex r =
new Regex(
@"{{(\w{0,15}?)}}");

body = r.Replace(emailBody, replaceField);


litOutput.Text +=
String.Format(
"{0}<hr />", body);

}

#endregion
This is alright and in many ways very scaleable. I’m not a fan of DataSets but in this instance it works nicely and does mean expanding the available fields at a later date would just be a matter of adding columns to the query.
How does this relate to accessing a property of an object using a string value instead? Well there was a catch, Julian wasn’t using a DataSet and didn’t want to, he had a collection of custom objects all ready and waiting. As he uses a code generator to generate his Data Access Layer and Business Logic Layer there was a method already exposed allowing you to search for a property by string but it's not a standard .Net method so I decided to work out how it was done.
The solution it turned out was a really rather elegant solution IMHO. Using reflection you can use the same concept as above but with custom objects and Robert is your father’s wife’s sister:
Reflection.aspx.cs
using System;
using System.Data;
using System.
Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Reflection;

public class TestObject
{...}

{
private int __Id;
private string __Name;
private string __Email;
private DateTime __JoinedDate;

public int Id
{...}
public string Name
{...}
public string Email
{...}
public DateTime JoinedDate
{...}

public TestObject(
int id,
string name,
string email, DateTime joinedDate)
{...}

{

__Id = id;

__Name = name;

__Email = email;

__JoinedDate = joinedDate;

}

public bool GetPropertyValueByName(
string propertyName)
{...}

{
object obj =
null;
return this.GetPropertyValueByName(propertyName,
false,
ref obj);

}

public bool GetPropertyValueByName(
string propertyName,
ref object val)
{...}

{
return this.GetPropertyValueByName(propertyName,
false,
ref val);

}

public bool GetPropertyValueByName(
string propertyName,
bool caseInsensitive,
ref object val)
{...}

{
PropertyInfo p =
null;
BindingFlags flags =
BindingFlags.Instance |
BindingFlags.Public |
BindingFlags.NonPublic;

//If it's a case-insensitive search then add the flag
if (caseInsensitive)

flags = flags |
BindingFlags.IgnoreCase;


p =
this.GetType().GetProperty(

propertyName,

flags,
null,
null,

Type.EmptyTypes,
null);

//Check the property exists and that it has read access
if (p !=
null && p.CanRead)
{...}

{
//There is a property that matches the name, we can read it so get it
val =
this.GetType().InvokeMember(

propertyName,
BindingFlags.GetProperty | flags,
null,
this,
null);

//We return true as the user may just want to check that it exists
return true;

}

return false;

}

}

public partial
class Reflection : System.Web.UI.Page
{...}

{
protected void Page_Load(
object sender,
EventArgs e)
{...}

{
if (!Page.IsPostBack)
{...}

{
Create the example email body

}

}

protected void btnSubmit_Click(
object sender,
EventArgs e)
{...}

{
if (Page.IsValid && !
String.IsNullOrEmpty(txtEmailBody.Text))
{...}

{

litOutput.Text =
"<h2>Output</h2>";
Perform some basic tests
#region Perform some basic tests

litOutput.Text +=
"<h3>Perform some basic tests:</h3>";
TestObject testObject =
new TestObject(
1,
"Tim",
"[email protected]", DateTime.Today);

object obj =
null;
if (testObject.GetPropertyValueByName(
"id",
false,
ref obj))

litOutput.Text +=
String.Format(
"<li>{0}</li>", obj);
else
litOutput.Text +=
"<li>Doesn't Exist</li>";

if (testObject.GetPropertyValueByName(
"name",
true,
ref obj))

litOutput.Text +=
String.Format(
"<li>{0}</li>", obj);
else
litOutput.Text +=
"<li>Doesn't Exist</li>";

if (testObject.GetPropertyValueByName(
"joineddate",
true,
ref obj))

litOutput.Text +=
String.Format(
"<li>{0}</li>", obj);
else
litOutput.Text +=
"<li>Doesn't Exist</li>";

if (testObject.GetPropertyValueByName(
"nothere",
true,
ref obj))

litOutput.Text +=
String.Format(
"<li>{0}</li>", obj);
else
litOutput.Text +=
"<li>Doesn't Exist</li>";

#endregion
Create a collection and add a couple of items
#region Create a collection and add a couple of items


List<TestObject> testObjects =
new List<TestObject>();

testObjects.Add(
new TestObject(
1,
"Tim",
"[email protected]", DateTime.Parse(
"01/02/2007")));

testObjects.Add(
new TestObject(
2,
"Jim",
"[email protected]", DateTime.Parse(
"20/02/2007")));

testObjects.Add(
new TestObject(
3,
"John",
"[email protected]", DateTime.Parse(
"02/03/2007")));

testObjects.Add(
new TestObject(
4,
"Gill",
"[email protected]", DateTime.Parse(
"01/04/2007")));

testObjects.Add(
new TestObject(
5,
"Bill",
"[email protected]", DateTime.Parse(
"11/02/2007")));

#endregion
Do the work
#region Do the work

//Format it with <pre> for simplicity
litOutput.Text +=
"<h3>Now for the reflection example:</h3><hr /><pre>";

//Loop through the rows
foreach (
TestObject t
in testObjects)
{...}

{

MatchEvaluator replaceField =
delegate(Match m)
{...};

//Create a new body as this'll be updated for each user
string body =
String.Empty;

//Find the fields within the main body -this can be any of the properties of the object
Regex r =
new Regex(
@"({{)(\w{0,15}?)(}})");

body = r.Replace(txtEmailBody.Text, replaceField);
//Output the example content (HtmlEncoded so not to hurt us!!)
litOutput.Text +=
String.Format(
"{0}<hr />", Server.HtmlEncode(body));

}


litOutput.Text +=
"</pre>";

#endregion
}

}

}
I’ve thrown up a quick demo if you want to test it out. I think in the longer run I’m going to look into having it generate some form of reporting system as that’d be seriously nice, but the suns out and I need to go for a paddle so that’ll have to wait for another day! So that's my first delve into reflection and so far I love it!
Liked this post? Got a suggestion? Leave a comment