Background
Recently, I had to work on a task where we needed to create a PDF version of a Silverlight page.The data to the markup (XAML) was from a C# object (yes, you guessed it! we used MVVM pattern) and we wanted to use that object for the PDF file. Below are the components we used to create a PDF file.
Dependencies
As mentioned above, you need:
I have excluded these DLLs in the sample code because one of the DLLs, wkhtmltox0
, of Pechkin package is about 29 MB resulting the attachment bigger. So, you will get build error if you just open and hit F5 (The source code can be opened in either VS 2010 or VS 2012).
Installing Dependencies
Using Nuget Package Manager
Manual Installation
You can also download the .zip version of these and manually add reference to your project. In this case, you should do the following settings in order to make the code work.
Add reference:
Add these files to the root of the project and set 'copy if newer':
Update web.config (or App.config in case you use it in Console):
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Common.Logging"
publicKeyToken="af08829b84f0328e" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.1.2.0" newVersion="2.1.2.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
Using the Code
The sample solution has three projects:
- WCF service
- Silverlight app
- Web app
PDFService
The class PdfService
offers two methods:
PdfBytes
- Accepts any string
content and converts as PDF bytes to the client.
EmployeePdf
- Specific to Employee
object and it converts the employee
data into an html string
which can then be passed to the PdfBytes
method to get PDF bytes.
public class PdfService : IPdfService
{
public PdfResult PdfBytes(string content)
{
var result = new PdfResult();
try
{
var oc = new ObjectConfig();
oc.SetPrintBackground(true); var footer = oc.Footer;
footer.SetFontSize(8);
footer.SetTexts("For internal use only", string.Empty, string.Empty);
result.Bytes = new SynchronizedPechkin(new GlobalConfig()).Convert(oc, content);
}
catch (Exception ex)
{
result.Error = ex.Message;
}
return result;
}
public PdfResult EmployeePdf(Employee employee)
{
string nustachTemplate = Path.Combine
(HostingEnvironment.ApplicationPhysicalPath, "App_Data", "EmployeeTemplate.html");
var employeeHtml = Render.FileToString(nustachTemplate, employee);
return PdfBytes(employeeHtml);
}
}
The Employee
object's data are injected into the Mustache html template. You can apply styles (only inline styles, style sheets cannot be linked) to design the page output.
<html>
<head>
<title>Employee Print</title>
<style>
body {
margin: 5px;
}
h3 {
text-align: center;
border-radius: 10px;
}
h4 {
background: silver;
border-radius: 5px;
padding-left: 10px;
}
</style>
</head>
<body>
<h3>Employee {{Name}}</h3>
<h4>Basic Details</h4>
<div>
<p>Name:</p>
<p>{{Name}}</p>
</div>
<div>
<p>Email:</p>
<p>{{Email}}</p>
</div>
<div>
<p>Address:</p>
<p>{{Address}}</p>
</div>
<h4>Skills</h4>
{{#Skills}}
<p>{{Name}}</p>
{{/Skills}}
{{^Skills}}
<p>No skills</p>
{{/Skills}}
<h4>Hobbies</h4>
{{#Hobbies}}
<p>{{Name}}</p>
{{/Hobbies}}
{{^Hobbies}}
<p>No hobbies</p>
{{/Hobbies}}
<h4>Jobs</h4>
{{#Jobs}}
<p>{{Company}}: as a {{Role}} for {{NoOfYears}} years.</p>
{{/Jobs}}
{{^Jobs}}
<p>No jobs</p>
{{/Jobs}}
</body>
</html>
And the Employee
class:
[DataContract]
public class Employee
{
public Employee()
{
Hobbies = new List<Hobby>();
Skills = new List<Skill>();
Jobs = new List<Job>();
}
[DataMember]
public string Name { get; set; }
[DataMember]
public string Address { get; set; }
[DataMember]
public string Email { get; set; }
[DataMember]
public List<Hobby> Hobbies { get; set; }
[DataMember]
public List<Skill> Skills { get; set; }
[DataMember]
public List<Job> Jobs { get; set; }
}
Here is the final PDF:
SilverlightApplication1
A client application using the PDFService
to generate Pdf. The code to call the service and save the file.
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
var dialog = new SaveFileDialog()
{
DefaultExt = "Adobe PDF Files(*.pdf)",
Filter = "PDF (*.PDF)|*.PDF",
FilterIndex = 2
};
if (dialog.ShowDialog() == true)
{
var client = new PdfServiceClient();
var emp = GetEmployee();
client.EmployeePdfCompleted += (s, ea) =>
{
if (ea.Error != null)
{
MessageBox.Show(string.Format("Error:{0}", ea.Error.Message));
return;
}
if (!string.IsNullOrWhiteSpace(ea.Result.Error))
{
MessageBox.Show(string.Format("Error:{0}", ea.Result.Error));
return;
}
using (System.IO.Stream stream = dialog.OpenFile())
{
stream.Write(ea.Result.Bytes, 0, ea.Result.Bytes.Length);
}
MessageBox.Show("Pdf saved");
};
client.EmployeePdfAsync(emp);
}
}
Pros and Cons
Pros
- Easy to build page content
- We can CSS to style the output
- No manual calculation of page count in PDF. Pages generated automatically based on the content
- Free libraries
Cons
Points of Interest
The Nustache and Mustache template are not really a requirement for creating a PDF file. You can create without it just by passing a string
or build your own html as far as it meets the requirement. I personally prefer Mustache as it is easy to build html, more importantly you can show or hide elements based on conditions while you have the full layout designed in HTML.
Before You Download the Sample Code
Again, the sample code will not work unless you follow the instructions mentioned in the Dependencies section at the top.
History
Finally...
I wanted to share my work on creating PDF and I did now. If somebody finds this interesting and it can be of help, then I will feel it is worth the effort. As always, comments/suggestions are welcome.