Friday, 31 July 2009
PDF Generation with PDFJet
I need to produce them in a number of different formats, pdf is the first one I tackled. All of the reports I need to produce use tables, so I needed to find a .Net pdf library that had this functionality. I took a look at what was on offer, open source, closed source and commercial. I went for a lib called PDFJet. This is a fairly mature Java pdf library that has been ported over to .Net for C# http://www.pdfjet.com/. Its fairly cheap and whilst it lacks a lot of in depth examples, there are quite a few available on their website. The support is really good too - if you have a question, the guys are able to provide feedback very quickly.
It is very well featured, but I am basically using it to generate tables from datatables. I have been using it in earnest for about a week now, so I will try and post some examples of how it can be used here. There are two ways of creating a table with PDFJet, you can use a delimited text file holding the data you need or you can build a table programatically. Doing it in the code is quite tricky, to do this you need to build a list of lists of type cell - not as easy as it sounds... However, I am very impressed with this library and the things it can do.
Automatically invoke methods from a given class
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.
Thursday, 4 June 2009
Reporting made easy
I initially though this was a joke, afterall this report had been publised each month for the last two years. Once I was told no, we really do do this, I needed to go and get myself a cup of sweet tea. After I had recovered, I started to speak to some of the analysts to try and find out the complete story behind this seemingly terrifying report, once I started to get some of the details behind this I came to the conclusion that the main reason for the length of time it takes to create this report was down to analysts doing the same thing every twenty-five days by hand. A prime opportunity to automate if ever there was one - and I had been after an excuse to create a .Net DAL for the team as well :).
There is nothing outstanding about this project at all, there isnt really any cutting edge tech being employed nor is there any new, fancy way of doing things being discussed. There is rarely anything taxing when it comes to generating reports. The most complicated thing you need to do is validate all the information that you are presenting, once the formula is there you are safe in the knowledge that your report will continue to churn out the required data for ever, the only time you will ever need to test it is when a new requirement is added to the schema of the report itself.
The uber report I am going to tackle just now is intended for publication via printed media. Because of this, I am confident in the knowledge that once the report has been created, no one will need to copy and past the data from it into another application - I know it gets distributed on paper. So, I know that I dont really need to provide a spreadsheet, csv, .doc or peice of XML initially. This is good news for me, as I only need to worry about one format, and I have decided to go for PDF. I had a look through the report a few days ago, when initially this task was given to me. Basically, its just a collection of graphs and tables, very little text, this just gets better as far as I am concerned. One format to worry about and very little wording to go into it - bliss!
So, what do I need for this to work? I need:
- A graphing lib, I am going for ZedGraph. Its open source and does everything I need.
- A PDF generation lob, for this I am going to use PFDJet. This is a commercial product, but there is also an open source version. As I dont need to do anything that fancy, it will do for me.
And thats pretty much it, all I will need to do is get the data I need from the db and use it to create some graphs and charts which go into a PDF report. Couldnt be simpler really, could it?
After a good planning meeting with the team lead and analyst I would be working with, it turns out that there is a similar system already in place for creating the basis for some of the data needed to create this report. After this session, I found that there is a monster Oracle DB somewhere in the depths of the core business that holds all the information we need. With the interesting bits to one side, I was then told a fairly comical story about Oracle, MySql and Access all doing a funny dance...
The only challenging thing really is the design of the report. Now that people have learned that I am automating some report generation, it seems like everyone has a preference as to how it should look. Right now, I am not concerned with fancy graphics or logos, right now the only important thing is getting the correct data into the report in a logical manner...