Till Q1 2013 one of the least extendable functionalities of Telerik’s ASP.NET HTML Editor was exporting the content to formats different than HTML. This includes PDF, RTF and Markdown. Our clients were not very happy with this, of course. So, I reviewed all the exports implementations (PDF, RTF and Markdown), and noticed a lot of similarities - both in the code and the usage scenarios. This led me to the thought that this is a good place to use the “Template” design pattern for my refactoring. In short, the “Template” design pattern follows the old Hollywood principle “Don't call us, we'll call you”. It also allows the functionality to be implemented according the open/close principle, which states “Software entities should be open for extension, but closed for modification". At this point after this short prep talk the main question remains:
Q1 2013 brought a major refactoring in the export functionality of RadEditor and now the two simple steps for achieving this goal are:
In this example I want to leverage the HTML to PDF conversion functionality implemented in our RadContols for WPF suite. This can be done quite easily, just follow my lead:
I am first going to start a new Telerik C# RadControls Web Application. I will make sure that the Telerik.Web.UI assembly reference points to the latest Q1 2013 version (2013.1.220.40), or you could use a version of RadControls for ASP.NET AJAX released after Q1 2013.
Then I will add references to the Telerik.Windows.Controls, Telerik.Windows.Data, Telerik.Windows.Documents, Telerik.Windows.Documents.FormatProviders.Html, Telerik.Windows.Documents.FormatProviders.Pdf, Telerik.Windows.Zip assemblies, which are part of our WPF suite. The version I am using is Q1 2014.
Next, I will start working through the default.aspx page. First, I will add an instance of RadEditor with a couple of ExportSettings configurations.
Markup<
telerik:RadEditor
ID
=
"RadEditor1"
runat
=
"server"
>
<
ExportSettings
FileName
=
"RadEditorExport"
OpenInNewWindow
=
"true"
></
ExportSettings
>
</
telerik:RadEditor
>
Second I will add Telerik’s ASP.NET Button, in which click event handler the editor’s content will be exported.
Markup<
telerik:RadButton
ID
=
"ExportButton"
Text
=
"Export to pdf"
OnClick
=
"ExportButton_Click"
runat
=
"server"
></
telerik:RadButton
>
The ExportButton click handler should look like the following code.
C#protected
void
ExportButton_Click(
object
sender, EventArgs e)
{
RadEditor1.ExportToPdf();
}
Next I need to implement the RadEditorExportTemplate abstract class, defined in the Telerik.Web.UI.Editor.Export namespace of the Telerik.Web.UI assembly. In order to do so I will add a class TelerikPdfExportTemplate.cs containing the code below:
C#using
System;
using
Telerik.Web.UI;
using
Telerik.Web.UI.Editor.Export;
using
Telerik.Windows.Documents.Model;
using
Telerik.Windows.Documents.FormatProviders.Pdf;
using
Telerik.Windows.Documents.FormatProviders.Html;
using
System.Threading;
using
System.Net;
using
System.IO;
public
class
TelerikPdfExportTemplate: RadEditorExportTemplate
{
public
TelerikPdfExportTemplate (RadEditor radEditor) :
base
(radEditor)
{
}
//no need to initialize the XmlContent
protected
override
void
InitializeXmlContent()
{
}
protected
override
string
GenerateOutput()
{
string
output =
""
;
var thread =
new
Thread(() =>
{
PdfFormatProvider provider =
new
PdfFormatProvider();
RadDocument document =
this
.ImportDocument();
this
.ProcessDocument(document);
byte
[] buffer = provider.Export(document);
output = ResponseWriteEncoding.GetString(buffer);
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
return
output;
}
private
RadDocument ImportDocument()
{
HtmlFormatProvider provider =
new
HtmlFormatProvider();
provider.ImportSettings.LoadImageFromUrl += ImportSettings_LoadImageFromUrl;
return
provider.Import(editor.Content);
}
void
ImportSettings_LoadImageFromUrl(
object
sender, LoadImageEventArgs e)
{
//This ensures images are properly loaded from url.
WebRequest req = WebRequest.Create(e.Url);
WebResponse response = req.GetResponse();
Stream imageStream = response.GetResponseStream();
using
(var memoryStream =
new
MemoryStream())
{
imageStream.CopyTo(memoryStream);
string
extension = Path.GetExtension(e.Url);
e.ImageElement.Init(memoryStream, extension);
e.ImageElement.Tag = e.Url;
e.Handled =
true
;
}
}
private
void
ProcessDocument(RadDocument document)
{
document.LayoutMode = DocumentLayoutMode.Paged;
//This reduces the space added by RadDocument's model before and after table elements.
RadDocumentEditor documentEditor =
new
RadDocumentEditor(document);
var tables = documentEditor.Document.EnumerateChildrenOfType<Table>();
foreach
(var table
in
tables)
{
Paragraph previousParagraph = table.PreviousSibling
as
Paragraph;
if
(previousParagraph !=
null
&& previousParagraph.IsEmpty)
{
documentEditor.Document.CaretPosition.MoveToEndOfDocumentElement(previousParagraph);
documentEditor.ChangeParagraphSpacingAfter(0);
documentEditor.ChangeFontSize(2);
}
Paragraph nextParagraph = table.NextSibling
as
Paragraph;
if
(nextParagraph !=
null
&& nextParagraph.IsEmpty)
{
documentEditor.Document.CaretPosition.MoveToEndOfDocumentElement(nextParagraph);
documentEditor.ChangeParagraphSpacingAfter(0);
documentEditor.ChangeFontSize(2);
}
}
}
protected
override
string
ContentType
{
get
{
return
"application/pdf"
; }
}
protected
override
string
FileExtension
{
get
{
return
".pdf"
; }
}
protected
override
ExportType ExportType
{
get
{
return
ExportType.Pdf; }
}
}
What can be noticed here is the parameterized constructor, which calls the default base constructor, where the protected RadEditor editor field is initialized. This field can be used to pass export settings defined in RadEditor to the RadEditorExportTemplate class. Also, it is used to provide the HTML content, which will be exported.
The only thing left to be done is injecting an instance of the TelerikPdfExportTemplate class in the editor. One place where this can be done is the Page_Load event handler in the Default.aspx page code behind.
C#protected
void
Page_Load(
object
sender, EventArgs e)
{
RadEditor1.SetPdfExportTemplate(
new
TelerikPdfExportTemplate(RadEditor1));
}
This is it. RadEditor PDF export now uses the Telerik HTML to PDF convertor provided in the Telerik’s WPF controls suite. You can download a trial here to get the needed assemblies to test it out.
Please note that RadDocument's model, which is a part of the library that is used in the example above, is quite different than the HTML DOM. For this reason you might experience some discrepancies or data loss when using this approach for creating PDF documents. This help article lists the currently supported tags and CSS properties.
We went through a simple example to show how you can plug an external library in the RadEditor’s export to PDF functionality. The source code of this sample is available for download here. The same approach can be used in the RTF and Markdown HTML content conversion of RadEditor. As a matter of fact, there will be a couple of posts following this one, where their implementation will be described in more detail.
Now, take the sample for a spin and let me know what you think!
Stanimir Patarinski is currently a senior software developer at Telerik Corp. He joined the company in the end of 2008 and his experience is centered on the ASP.NET AJAX products from Telerik, particularly the RadEditor control. He is an MCPD certified Web, SharePoint and Azure developer and has extensive knowledge about the integration of RadControls in third-party applications including SharePoint 2007, 2010, 2013.