Introduction
PdfReport is a code-first reporting engine, which is built on top of the iTextSharp and EPPlus libraries. It's compatible with both .NET 3.5+ Web and Windows applications. PdfReport supports a wide range of data sources from data tables to in-memory strongly typed lists without needing a database. It saves you time from searching and learning a lot of tips and tricks of iTextSharp and EPPlus libraries. It's designed to be compatible with RTL languages.
How to start using PdfRport
To create your first PdfReport:
- Create a new Class Library project in Visual Studio. We will use it as the base report classes container for both Windows and Web applications.
- Then add new references to the following assemblies: PdfReport, iTextSharp, and EPPlus. You can download them from
http://pdfreport.codeplex.com/releases/
Or just use the NuGet PowerShell console to add these references automatically:
PM> Install-Package PdfReport
http://nuget.org/packages/PdfReport/
- Add the following classes to the Class Library project:
using System.Web;
using System.Windows.Forms;
namespace PdfReportSamples
{
public static class AppPath
{
public static string ApplicationPath
{
get
{
if (isInWeb)
return HttpRuntime.AppDomainAppPath;
return Application.StartupPath;
}
}
private static bool isInWeb
{
get
{
return HttpContext.Current != null;
}
}
}
}
We will use this class to specify the location of the produced PDF file. It needs the following references as well:
- System.Windows.Forms.dll
- System.Web.dll
using System;
namespace PdfReportSamples.IList
{
public class User
{
public int Id { set; get; }
public string Name { set; get; }
public string LastName { set; get; }
public long Balance { set; get; }
public DateTime RegisterDate { set; get; }
}
}
using System;
using System.Collections.Generic;
using PdfReportSamples.Models;
using PdfRpt.Core.Contracts;
using PdfRpt.FluentInterface;
namespace PdfReportSamples.IList
{
public class IListPdfReport
{
public IPdfReportData CreatePdfReport()
{
return new PdfReport().DocumentPreferences(doc =>
{
doc.RunDirection(PdfRunDirection.LeftToRight);
doc.Orientation(PageOrientation.Portrait);
doc.PageSize(PdfPageSize.A4);
doc.DocumentMetadata(new DocumentMetadata { Author = "Vahid",
Application = "PdfRpt", Keywords = "IList Rpt.",
Subject = "Test Rpt", Title = "Test" });
})
.DefaultFonts(fonts =>
{
fonts.Path(Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\arial.ttf",
Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\verdana.ttf");
})
.PagesFooter(footer =>
{
footer.DefaultFooter(DateTime.Now.ToString("MM/dd/yyyy"));
})
.PagesHeader(header =>
{
header.DefaultHeader(defaultHeader =>
{
defaultHeader.RunDirection(PdfRunDirection.LeftToRight);
defaultHeader.ImagePath(AppPath.ApplicationPath + "\\Images\\01.png");
defaultHeader.Message("Our new rpt.");
});
})
.MainTableTemplate(template =>
{
template.BasicTemplate(BasicTemplate.ClassicTemplate);
})
.MainTablePreferences(table =>
{
table.ColumnsWidthsType(TableColumnWidthType.Relative);
table.NumberOfDataRowsPerPage(5);
})
.MainTableDataSource(dataSource =>
{
var listOfRows = new List<User>();
for (int i = 0; i < 200; i++)
{
listOfRows.Add(new User { Id = i, LastName = "LastName " + i,
Name = "Name " + i, Balance = i + 1000 });
}
dataSource.StronglyTypedList(listOfRows);
})
.MainTableSummarySettings(summarySettings =>
{
summarySettings.OverallSummarySettings("Summary");
summarySettings.PreviousPageSummarySettings("Previous Page Summary");
summarySettings.PageSummarySettings("Page Summary");
})
.MainTableColumns(columns =>
{
columns.AddColumn(column =>
{
column.PropertyName("rowNo");
column.IsRowNumber(true);
column.CellsHorizontalAlignment(HorizontalAlignment.Center);
column.IsVisible(true);
column.Order(0);
column.Width(1);
column.HeaderCell("#");
});
columns.AddColumn(column =>
{
column.PropertyName<User>(x => x.Id);
column.CellsHorizontalAlignment(HorizontalAlignment.Center);
column.IsVisible(true);
column.Order(1);
column.Width(2);
column.HeaderCell("Id");
});
columns.AddColumn(column =>
{
column.PropertyName<User>(x => x.Name);
column.CellsHorizontalAlignment(HorizontalAlignment.Center);
column.IsVisible(true);
column.Order(2);
column.Width(3);
column.HeaderCell("Name");
});
columns.AddColumn(column =>
{
column.PropertyName<User>(x => x.LastName);
column.CellsHorizontalAlignment(HorizontalAlignment.Center);
column.IsVisible(true);
column.Order(3);
column.Width(3);
column.HeaderCell("Last Name");
});
columns.AddColumn(column =>
{
column.PropertyName<User>(x => x.Balance);
column.CellsHorizontalAlignment(HorizontalAlignment.Center);
column.IsVisible(true);
column.Order(4);
column.Width(2);
column.HeaderCell("Balance");
column.ColumnItemsTemplate(template =>
{
template.TextBlock();
template.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
});
column.AggregateFunction(aggregateFunction =>
{
aggregateFunction.NumericAggregateFunction(AggregateFunction.Sum);
aggregateFunction.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
});
});
})
.MainTableEvents(events =>
{
events.DataSourceIsEmpty(message: "There is no data available to display.");
})
.Export(export =>
{
export.ToExcel();
export.ToCsv();
export.ToXml();
})
.Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\RptIListSample.pdf"));
}
}
}
You can find its latest version here too.
To use this class and create a new PDF report file, we can write:
var rpt = new IListPdfReport().CreatePdfReport();
public void SqlDataReader(string connectionString, string sql, params object[] parametersValues)
public void AccessDataReader(string filePath, string password, string sql, params object[] parametersValues)
public void OdbcDataReader(string connectionString, string sql, params object[] parametersValues)
public void GenericDataReader(string providerName, string connectionString, string sql, params object[] parametersValues)
It's possible to write parametric queries in all of the above methods. These parameters should start with the @ symbol. Here is a quick sample which shows how to work with SQLite databases in PdfReport:
dataSource.GenericDataReader(
providerName: "System.Data.SQLite",
connectionString: "Data Source=" + AppPath.ApplicationPath + "\\data\\blogs.sqlite",
sql: @"SELECT [url], [name], [NumberOfPosts], [AddDate]
FROM [tblBlogs]
WHERE [NumberOfPosts]>=@p1",
parametersValues: new object[] { 10 }
);
Add a reference to the
System.Data.SQLite assembly and then use the above generic data reader. The same rule applies to MySQL and other databases.
- The
MainTableSummarySettings
method determines the auto generated summary/total labels and their position to show. It's optional. - By using the optional
MainTableColumns
method, it's possible to determine the exact columns of the report's grid. Each column should be present in the data source. Also it's possible to define the calculated fields as well. It will be discussed in other How-To's later. In the MainTableColumns
method, you can specify the related property of the column, its width, visibility, order, and so on. Here by using the ColumnItemsTemplate
method, we can determine the type of the current field and how it should be displayed. If it should be displayed as a text, use the template.TextBlock()
method (it's the default method). Also there are some other built-in cell templates such as image, hyperlinks, etc. It's possible to use custom column templates by implementing the IColumnItemsTemplate
interface too.
If you want to format the cell's value before rendering, use the template.DisplayFormatFormula
method. It's a callback method, which gives you the actual value of the cell and then you can format it and return the final result to show on the report. - By using the
column.AggregateFunction
method, we can determine the related aggregate method of the current column.
There are some predefined numeric aggregate functions available in the PdfReport library. Also it's possible to write custom ones by implementing the IAggregateFunc
interface. - The
MainTableEvents
method provides access to the internal events of the main grid. For instance if the data source is empty, the DataSourceIsEmpty
event will be raised. - Also it's possible to export the main table's data as Excel, CSV, XML, etc. files. All of these exported files will be embedded in the final PDF file automatically.
How to create auto generated/dynamic columns
Specifying the MainTableColumns
method and all of its definitions is arbitrary in PdfReport. Just omit this part and then the final report will be created dynamically based on the available columns in the provided data source. This feature gives us great flexibility, but after some time we need to customize these kinds of reports: how to format DateTimes,
how to add the total number of numeric fields and so on.
Here are some tips about customizing the auto generated columns:
a) Use aliases to specify the header cells:
If you are using the SQL based data sources such as GenericDataReader
, to customize the header cells just define the column aliases in your final SQL:
SELECT [NumberOfPosts] as 'Number of posts'
FROM [tblBlogs]
WHERE [NumberOfPosts]>=@p1
b) Specifying the rendering conventions, based on the data types of columns
By using the MainTableAdHocColumnsConventions
method, it's possible to alter the rendering conditions of the dynamic columns. In
the MainTableAdHocColumnsConventions
method, we can include the auto generated row column in the final report:
adHocColumns.ShowRowNumberColumn(true);
adHocColumns.RowNumberColumnCaption("#");
Or it's possible to format a cell's value based on its type:
adHocColumns.AddTypeDisplayFormatFormula(
typeof(DateTime),
data => { return PersianDate.ToPersianDateTime((DateTime)data); }
);
Here we are altering the rendering value of all of the DateTime
columns.
adHocColumns.AddTypeAggregateFunction(
typeof(Int64),
new AggregateProvider(AggregateFunction.Sum)
{
DisplayFormatFormula = obj => obj == null ? string.Empty : string.Format("{0:n0}", obj)
});
Or we can determine the specific AggregateFunction
of the given data type.
c) Using data annotations to define column properties
If you are using a generic list data source, it's possible to omit the MainTableColumns
method and
all of its definitions by replacing them with data annotations:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using PdfReportSamples.Models;
using PdfRpt.Aggregates.Numbers;
using PdfRpt.ColumnsItemsTemplates;
using PdfRpt.Core.Contracts;
using PdfRpt.Core.Helper;
using PdfRpt.DataAnnotations;
namespace PdfReportSamples.DataAnnotations
{
public class Person
{
[IsVisible(false)]
public int Id { get; set; }
[DisplayName("User name")]
[ColumnItemsTemplate(typeof(TextBlockField))]
public string Name { get; set; }
[DisplayName("Job title")]
public JobTitle JobTitle { set; get; }
[DisplayName("Date of birth")]
[DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}")]
public DateTime DateOfBirth { get; set; }
[DisplayName("Date of death")]
[DisplayFormat(NullDisplayText = "-", DataFormatString = "{0:MM/dd/yyyy}")]
public DateTime? DateOfDeath { get; set; }
[DisplayFormat(DataFormatString = "{0:n0}")]
[CustomAggregateFunction(typeof(Sum))]
public int Salary { get; set; }
[IsCalculatedField(true)]
[DisplayName("Calculated Field")]
[DisplayFormat(DataFormatString = "{0:n0}")]
[AggregateFunction(AggregateFunction.Sum)]
public string CalculatedField { get; set; }
[CalculatedFieldFormula("CalculatedField")]
public static Func<IList<CellData>, object> CalculatedFieldFormula =
list =>
{
if (list == null) return string.Empty;
var salary = (int)list.GetValueOf<Person>(x => x.Salary);
return salary * 0.8;
};
}
}
- If you don't want to show a property in the final report, use the
[IsVisible(false)]
attribute. DisplayName
attribute will be used to define the header cells of the report. - Specifying the
ColumnItemsTemplate
attribute is optional and if it's not defined,
TextBlockField
will be used automatically.
Other predefined column cell templates are defined in the PdfRpt.ColumnsItemsTemplates
namespace.
- To format the displayed dates or numbers, use the
DisplayFormat
attribute. - To add the total rows, specify the
CustomAggregateFunction
attribute. There are some built-in aggregate functions in the
PdfRpt.Aggregates.Numbers
namespace. - To define a calculated field/column, specify the
[IsCalculatedField(true)]
attribute. And then add a new static field (not a property) to define the related formula. This new field should be decorated with the
CalculatedFieldFormula
attribute to determine the property name of the calculated field.
Here are some full samples about auto generated columns:
How to define a calculated column
Some times we need to create a new column which is not present in the data source, based on the values of other columns. There are
two types of calculated columns in PdfReport:
a) Row numbers columns
Just set column.IsRowNumber(true);
. And then a new row number column which is not included in the data source will be available in the final report.
b) To define a custom calculated column, we need to specify its calculation formula by setting the column.CalculatedField method:
columns.AddColumn(column =>
{
column.PropertyName("CF1");
column.CalculatedField(
list =>
{
if (list == null) return string.Empty;
var name = list.GetSafeStringValueOf<User>(x => x.Name);
var lastName = list.GetSafeStringValueOf<User>(x => x.LastName);
return name + " - " + lastName;
});
column.HeaderCell("Full name");
column.Width(3);
column.CellsHorizontalAlignment(HorizontalAlignment.Center);
column.IsVisible(true);
column.Order(4);
});
The column.CalculatedField
method's argument gives us the list of values of the current row:
public void CalculatedField(bool isCalculatedField, Func<IList<CellData>, object> calculatedFieldFormula)
Now we have time to build the current cell's value based on the other column values. There are some helper methods defined in the
PdfRpt.Core.Helper
namespace such as GetSafeStringValueOf
to help working with
IList<CellData>
data more easily. Defining a PropertyName
is mandatory, but in this case it can be an arbitrary text.
Here is the full sample which shows how to define a calculated column:
CalculatedFields
How to show images in PdfReport
Usually we have two kinds of images in reports:
a) Images loaded from the file system
To show these kind of images we need to change the default
ColumnItemsTemplate
which is TextBlock
to
ImageFilePath
:
columns.AddColumn(column =>
{
column.PropertyName<ImageRecord>(x => x.ImagePath);
column.CellsHorizontalAlignment(HorizontalAlignment.Center);
column.IsVisible(true);
column.Order(2);
column.Width(3);
column.HeaderCell("Image");
column.ColumnItemsTemplate(t => t.ImageFilePath(defaultImageFilePath: string.Empty, fitImages: false));
});
Here defaultImageFilePath
is the default image path in the case of missing images.
b) Images stored in databases
Showing images stored as binary data in databases is similar to (a). We just need to use the suitable ColumnItemsTemplate
which is the ByteArrayImage
template:
columns.AddColumn(column =>
{
column.PropertyName("thumbnail");
column.CellsHorizontalAlignment(HorizontalAlignment.Center);
column.IsVisible(true);
column.Order(5);
column.HeaderCell("Image");
column.ColumnItemsTemplate(t => t.ByteArrayImage(defaultImageFilePath: string.Empty, fitImages: false));
});
Here you can find the complete samples of (a) and (b):
Conditional formatting in PdfReport
Suppose we want to display the list of personnel in a year. In this report each month cell with month=7 should be displayed with a different color. To achieve this goal we can use the template.ConditionalFormatFormula
method:
columns.AddColumn(column =>
{
column.PropertyName("Month");
column.CellsHorizontalAlignment(HorizontalAlignment.Center);
column.IsVisible(true);
column.Order(2);
column.Width(2);
column.HeaderCell("Month");
column.ColumnItemsTemplate(template =>
{
template.TextBlock();
template.ConditionalFormatFormula(list =>
{
var cellValue = int.Parse(list.GetSafeStringValueOf("Month", nullValue: "0"));
if (cellValue == 7)
{
return new CellBasicProperties
{
PdfFontStyle = DocumentFontStyle.Bold | DocumentFontStyle.Underline,
FontColor = new BaseColor(System.Drawing.Color.Brown),
BackgroundColor = new BaseColor(System.Drawing.Color.Yellow)
};
}
return new CellBasicProperties { PdfFontStyle = DocumentFontStyle.Normal };
});
});
});
template.ConditionalFormatFormula
is a callback method which gives us the list of the current row's data. Now based on the value of the month (or other properties) we can return the "new CellBasicProperties" with a different font color,
style, etc. You can find the related sample here.
How to create custom main table's templates
There are some built-in report templates in the PdfReport library such as BasicTemplate.RainyDayTemplate
,
BasicTemplate.SilverTemplate
, etc. Also it's possible to create the new main table's templates by implementing the ITableTemplate
interface. For instance:
using System.Collections.Generic;
using System.Drawing;
using iTextSharp.text;
using PdfRpt.Core.Contracts;
namespace PdfReportSamples.HexDump
{
public class GrayTemplate : ITableTemplate
{
public HorizontalAlignment HeaderHorizontalAlignment
{
get { return HorizontalAlignment.Center; }
}
public BaseColor AlternatingRowBackgroundColor
{
get { return new BaseColor(Color.WhiteSmoke); }
}
public BaseColor CellBorderColor
{
get { return new BaseColor(Color.LightGray); }
}
public IList<BaseColor> HeaderBackgroundColor
{
get { return new List<BaseColor> { new BaseColor(
ColorTranslator.FromHtml("#990000")),
new BaseColor(ColorTranslator.FromHtml("#e80000")) }; }
}
public BaseColor RowBackgroundColor
{
get { return null; }
}
public IList<BaseColor> PreviousPageSummaryRowBackgroundColor
{
get { return new List<BaseColor> { new BaseColor(Color.LightSkyBlue) }; }
}
public IList<BaseColor> SummaryRowBackgroundColor
{
get { return new List<BaseColor> { new BaseColor(Color.LightSteelBlue) }; }
}
public IList<BaseColor> PageSummaryRowBackgroundColor
{
get { return new List<BaseColor> { new BaseColor(Color.Yellow) }; }
}
public BaseColor AlternatingRowFontColor
{
get { return new BaseColor(ColorTranslator.FromHtml("#333333")); }
}
public BaseColor HeaderFontColor
{
get { return new BaseColor(Color.White); }
}
public BaseColor RowFontColor
{
get { return new BaseColor(ColorTranslator.FromHtml("#333333")); }
}
public BaseColor PreviousPageSummaryRowFontColor
{
get { return new BaseColor(Color.Black); }
}
public BaseColor SummaryRowFontColor
{
get { return new BaseColor(Color.Black); }
}
public BaseColor PageSummaryRowFontColor
{
get { return new BaseColor(Color.Black); }
}
public bool ShowGridLines
{
get { return true; }
}
}
}
To use this new template, we can write:
.MainTableTemplate(template =>
{
template.CustomTemplate(new GrayTemplate());
})
Some tips and tricks:
- Colors in the iTextSharp library are defined by the
BaseColor
class. If you want to convert the colors of
System.Drawing
to BaseColor
, just pass it to the
BaseColor
's constructor:
var blackColor = new BaseColor(Color.Black);
There are some useful helper methods in .NET classes such as ColorTranslator.FromHtml
to convert and use the HTML colors. If you want to define a transparent color here, just return null
. In the ITableTemplate
interface, some colors are defined as a list. These properties can accept one or max
two colors. If two colors are specified, an automatic gradient
of these colors will be shown on the report.
How to use HTML to create custom cell templates
Suppose we have a list of users with their names and photos. We want to show the name and photo of each user in one cell and not
two separate cells.
The built-in cell templates of PdfReport are suitable for displaying only one object per cell. To define a custom cell template, we need to implement the
IColumnItemsTemplate
interface or use a shortcut:
columns.AddColumn(column =>
{
column.PropertyName("User");
column.CellsHorizontalAlignment(HorizontalAlignment.Center);
column.IsVisible(true);
column.Order(1);
column.Width(3);
column.HeaderCell("User");
column.CalculatedField(list =>
{
var user = list.GetSafeStringValueOf("User");
var photo = new Uri(list.GetSafeStringValueOf("Photo"));
var image = string.Format("<img src='{0}' />", photo);
return
@"<table style='width: 100%; font-size:9pt;'>
<tr>
<td>" + user + @"</td>
</tr>
<tr>
<td>" + image + @"</td>
</tr>
</table>
";
});
column.ColumnItemsTemplate(template =>
{
template.Html();
});
});
Here iTextSharp's HTMLWorker
class is used behind the scenes. By using CalculatedField
, we can inject our new value of a cell and then process it by the selected ColumnItemsTemplate
.
Note: HTML to PDF capabilities of iTextSharp's HTMLWorker
class are very limited, and don't expect too much about it.
You can find this sample here.
More guides and samples
PdfReport comes with more than 40 samples. Please download
its source code from CodePlex and check out its samples folder.