It is a common requirement to be able to automatically generate documents in an application. This post shows how I’ve used standard .Net client reporting libraries to allow me to create Pdf and Word documents in my applications.
The code for this post can be found on my GitHub profile: https://github.com/davehawes/RDLC-Pdf-Generator
High Level Design
The idea is not very complicated. You need to be able to design the data to go in the report, design the layout of the report and place the data in the appropriate places, and then merge the two together to create the document.
Designing The Data
By using an ADO Dataset it is easy to define the data you want to put into a report. It supports strong types and is easy to design in Visual Studio. This can be seen as a Data Transfer Object (DTO) for getting data from your application into the Report.
Designing The Report
Microsoft .Net has a file type called an RDLC (Report Definition Language Client). This is the same file definition that is used in Microsoft Sql Server Reporting Services but is designed to run on a client machine instead of the server. You can create a new one of these files by clicking File –> Add New Item:
Once you have the empty report you can link it to the DataSet you have designed:
*Important* – Make sure you give the DataSet the same name as the DataTable you want it to use inside of the dataset. In this example it is ‘MasterData’. Failure to do this will mean the code that merges the data with the report cannot link the two things up and throws an error.
If you cannot see the ‘Report Data’ window it is because Microsoft like you to work hard for your money and have cleverly hidden it unless you know the special combination of things to click. First you have to select the Report on the workspace then click ‘View –> ReportData’ which is right at the bottom of the menu options.
This then lists all the DataSets in your project and you can select the one that you want to use.
You can then drag and drop the columns onto the report to make it look pretty:
Merging the two together
So we have done all the designing and now we need to create the Pdf. The first step is to get an instance of your DataSet full of records to be put in the report.
private static Dataset CreateSample1Dataset()
var dataset = new Reports.Sample1.Dataset();
var masterDataRow = dataset.MasterData.NewMasterDataRow();
masterDataRow.Id = 1;
masterDataRow.Name = "Master Data Row 1";
masterDataRow.Description = "Description about data row 1";
I have then created a Facade class to hold the data and also the file path to the rdlc file. Create one of these objects and pass in the populated dataset and the file path to the rdlc file you want to use:
var reportDefinition = new ReportFromFileDefinitionFacade(CreateSample1Dataset(), "Reports\\Sample1\\Template.rdlc");
(If you are using my project notice that I’ve set the rdlc’s Build Action to ‘Content’ and the Copy to Output Directory to ‘Copy if newer’. You can put in the file path to where ever you have deployed your rdlc files.)
Once you have got the this Facade class populated then you can just pass it into my generator class to produce a pdf:
byte pdfFile = Reports.Generator.CreatePdf(reportDefinition);
That’s it. You can then do what you want with the byte.
Master Detail Reports (using sub reports)
A common requirement is to have a sub report in your main report. This can be achieved as well but you have to be careful with your naming conventions.
Design the Data
Create another datatable in the dataset to hold the data for the sub report. Notice that I have not created a relationship between the two datatables as this is done in the rdlc file later on:
Next create a new rdlc file that will be your sub report:
Notice that I’ve named this Template_Detail, this is the same name I gave to the DataTable in the dataset.
In the main report drop a subreport control onto the layout and set the sub report properties:
Be consistent with your naming here, I’ve used ‘Template_Detail’ in both places in this example. In the parameters section add a new parameter to create the relationship between the master – detail datatables. Make this the same name as the Foreign Key field in the Template_Detail dataset, in this example ‘MasterDataId’:
You then need to add this parameter to the sub report. Make sure the name is the same and the type is the same as it is defined in the DataTable:
That is it. Now all you need to do is populate the DataSet as before, but this time with detail records, and pass it in with the path to the master report. You have to make sure that the sub reports are deployed in the same folder as the master report so that it gets resolved correctly.
Passing the rdlc file as a byte
It is not always possible to access the file system to read the rdlc file and you might want to get it from somewhere else, such as a database, as a byte. You can do this by using the ReportFromStreamDefinitionFacade class I’ve created.
You have to set the reports up in exactly the same way but instead of passing in a file path to the location you pass in a byte that contains the rdlc file. If you are doing this with sub reports then you need to add each sub report in the SubReports list on the parent report, passing in the name that matches the datatable name that it needs to get its data from:
var report = new ReportFromStreamDefinitionFacade(dataset, File.ReadAllBytes("Reports\\MasterDetailSample\\Template.rdlc"));
report.SubReports.Add(new SubReportFromStreamDefinitionFacade(File.ReadAllBytes("Reports\\MasterDetailSample\\Template_Detail.rdlc"), "Template_Detail"));
That’s it! I hope people find this utility useful