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:

How to plug a custom HTML to PDF conversion library in RadEditor export functionality?

Q1 2013 brought a major refactoring in the export functionality of RadEditor and now the two simple steps for achieving this goal are:

  1. Create a class, which implements the RadEditorExportTemplate abstract class defined in the Telerik.Web.UI.Editor.Export namespace of the Telerik.Web.UI assembly. In general this involves adding a default parameterized constructor, which takes a RadEditor parameter, and overriding the GenerateOutput method and the ContentType, FileExtension, ExportType property getters.
  2. Use the SetPdfExportTemplate RadEditor’s method, to inject an instance of the previously created class in the editor’s export to PDF functionality.

Building a Sample Project

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.

Conclusion

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!


About the Author

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.

Related Posts

Comments

Comments are disabled in preview mode.