Friday 31 July 2009

Automatically invoke methods from a given class

Had an interesting problem at work this week. I have been working on a way to automatically generate a number of (lengthy) reports in a number of different formats. Each report has a number of sections that in turn hold a number of subsections. Each subsection holds either a chart or a table. Each report needs to be presented as either a pdf, a doc, an xls or a csv file. Some of the sections in each report are used in other reports.

I came round to designing each section as a class, in each class is a method that generates either a datatable to build a table from or a chart generated as an image. I wanted the generation of these datatables and images to be as generic as possible, the methods called provide the objects I needed but the way I call them needed to be as generic as I could get it. So, instead of putting together a method that calls all of these other methods I came up with the idea of building a list of all the methods I need to call in order to generate a report. To do this, I used reflection.

Firstly, I needed to get a list of all the method names I wanted to call.
List<MethodInfo> sectionNames = new List<MethodInfo>();
Sections s = new Sections(conn);
Type t = s.GetType();
MethodInfo[] mi = t.GetMethods();
foreach (MethodInfo method in mi)
{
sectionNames.Add(method);
}


In this example, Sections is the name of the class holding the methods I need to generate a report. It populates its datatables from a database, so the conn variable holds the connection string needed. There are a lot of sections in the report I was working on, so I split each section up as partial classes. Using GetMethods () on my class returns all of the available methods within that class. With that information, I simply create a list of type MethodInfo and add each element of the array I have created to the list. I dont really need to do this, but at some point I am going to want to sort the list, which is a little easier than sorting the array (from my perspective). I now have a list of all the method names available in my class. The next thing I do is get rid of some of the generic methods, like GetType(). I dont know if there is a cleaner way of doing this, so all I am doing right now is deleting a range from the list:


sectionNames.RemoveRange(8, 4);

Now I have almost everything I need. Next up, I need to invoke the method and capture its return. The first type of report I tackled was a pdf. The library I am using to create these pdf's uses delimited text files to create tables (it can be done programatically, but I havent mastered this yet). So, I need to create a dictionary holding the name of the text file I want to create and the name of the method to create it from. Now, I also mentioned that my report will also hold charts generated as images - so the dictionary I create needs to hold a string and an object, not a string and a datatable or any other type. It may not be the cleanest way to do it, but I can cast the object as a datatable later on, anyway...
Dictionary<string, Object> content = new Dictionary<string, Object>();


Thats my dictionary ready to go, I just need to populate it with filenames and object. This isnt as hard as it seems, because my list contains type MethodInfo I can use the Invoke method to call all of the methods in the list. Thats a bit of a mouthful really, so it is better to demonstrate:


foreach (MethodInfo method in sectionNames)
{
object obj = method.Invoke(s, null);
content.Add(method.Name + ".txt", obj);
}


I loop through each MethodInfo type in my list. I create an object called obj, this becomes the return from each of the methods I call using Invoke. Once called and invoked, I add the result to my dictionary, on success the dictionary contains a key value pair representing a string filename and an object. You can see in the example above that the filename is always a .txt - this is bad when it comes to images (I am not creating any just yet). But, this can be worked around by getting the type of the object etc. However, right now this is all I need to create my text files to use with my pdf generation. I simply loop through the dictionary creating a textfile from the contents of the object (which is a datatable dont forget), the string is the filename the text file is created with.

Its not very efficient just now, I need to manage the methods in the dictionary - I need to remove the generic ones prior to adding them and I need to sort them in order, both things I either do manually or not at all right now. But this is a nice little example of how methods can be gathered from a class and then dynamically invoked. The complete source for this example is as follows:


/// <summary>
/// Creates a Dictionary of strings and object representing the content we want to build
/// </summary>
/// <returns>a Dictionary of strings and objects holding the filename and the datatable</returns>
private Dictionary<string, Object> Contents()
{
List<MethodInfo> sectionNames = new List<MethodInfo>();
Sections s = new Sections(conn);
Type t = s.GetType();
MethodInfo[] mi = t.GetMethods();
foreach (MethodInfo method in mi)
{
sectionNames.Add(method);
}
sectionNames.RemoveRange(8, 4);
sectionNames.Sort();
Dictionary<string, Object> content = new Dictionary<string, Object>();
foreach (MethodInfo method in sectionNames)
{
object obj = method.Invoke(s, null);
Console.WriteLine(obj);
content.Add(method.Name + ".txt", obj);
}
return content;

I will also post the file creation method that works with this after the weekend as well as some bits and peices to do with the pdf library I am using. But hopefully this should be enough to get someone else on the right track if they are also considering such a soloution.

No comments:

Post a Comment