Hi,
I've developed a web-form module for DNN which is working nicely (thanx to everyone for the guidance, samples etc.)
My customer has now ask for a new feature - to export a page (module only, with styling etc.) to PDF. There are a multitude of HTML to PDF controls available (OS / paid) that I can use, but they all require a reference to the source HTML. I don't want to export the entire page (menus, header bar etc.) - just the content of my module (one specific control ascx) with the various CSS styles so that it displays nicely.
How would I go about doing this?
My basic module control has an empty container that I load a control (that inherits from PortalModuleBase) into when the page loads - based on the 'type' of request being performed by module. This is working and dynamically loads the various controls as I navigate through my modules.
On one of the controls (when viewing the details of an item), I have added a link that the user can click on to Export the details. I want this to load my ExportControl.ascx into memory, render it as HTML (ideally to a stream, but for now an HTML file will work as well) that I can then pass to the PDF library to generate the PDF.
I have the following in place:
When I click on the link, my Export control is loaded and the HTML is rendered to a file (sounds good so far). However:
How do I ensure that the control's Page_Load event etc. all fires and populates as expected? Is it possile to load this in another page (like target="_blank") and then somehow export to PDF (can't force them to use Chrome)? What is the recommended method to achieve this?
When I click on the link - it calls a static method in a helper class to generate a MemoryStream with the rendered HTML and writes to a file (so I can debug etc.): using (var memoryStream = ModuleRenderHelper.RenderModuleToMemoryStream("~/DesktopModules/MyModule/Manager/Controls/ExportItemDetails.ascx", this)) { // Example: Convert the stream to a string for display using (var reader = new StreamReader(memoryStream)) { string renderedHtml = reader.ReadToEnd(); File.WriteAllText(@"C:\Users\Public\Documents\MyPage.htm", renderedHtml); } }
My helper method is as follows:
public static MemoryStream RenderModuleToMemoryStream(string modulePath, PortalModuleBase control) { // Step 1: Load the ASCX module control var moduleControl = control.LoadControl(modulePath) as RoQIModuleBase;
if (moduleControl == null) { throw new InvalidOperationException("Unable to load the module control."); } var page = control.Page;
// Step 2: Prepare a MemoryStream to capture the HTML output var memoryStream = new MemoryStream();
using (var streamWriter = new StreamWriter(memoryStream, Encoding.UTF8, 32768, true)) { using (var htmlWriter = new HtmlTextWriter(streamWriter)) { moduleControl.ModuleConfiguration = control.ModuleConfiguration; // Step 3: Render the module control directly moduleControl.RenderControl(htmlWriter); htmlWriter.Flush(); }
// Reset the memory stream position to the beginning for reading memoryStream.Position = 0;
// Return the memory stream containing the rendered HTML return memoryStream; } }
Any help would be greatly appreciated.
TIA, Alon
Perhaps not the answer you're looking for but I created a similar thing for https://www.schutte-usa.c...Id/468/Cable%20Ties#
If you hit the download button, it creates a PDF of the product based on the values in the database. In this particular case, the pdf is created as an action within the module ActionForm. As I use Plant an App, this module is part of the suite. As you can see, the PDF does not print the page but is entirely custom. Als taking into account the product category and the language. If ActionForm is not sold seperately, you might want to take a look at Live Forms and the PDF add on. Maybe a bit fewer options but much cheaper.
If you want to build it yourself, consider using 2sxc. A lot of good stuff is already in that platform and pdfStream as well.
Cheers
There are a lot of ways to do this, and the HTML can be output and/or fed into the various PDF generation APIs in many ways too. We recently did this, and used a localized solution, where most of the HTML was contained in a localization file to allow for editing, and it gets fed through a method which replaces some placeholders, before presenting the PDF as a download. It only depends on whether or not the PDF generator is a good one, and how you want the UX to be. 😎
Posted By wizard on 1/24/2025 7:34 PM
Hi Will,
Thanx for responding - much appreciated.
The / control PDF is supposed to list the history of a 'conversation' (much like this one) including details of attachments (which will be downloaded in a zip file). Only the field labels could really be localized - the rest is simply HTML text captured in CKEditor for each conversation entry.
As for the UX - on my current form I have added an Export button. Clicking this should render the export control (rendered optimised for PDF), generate the PDF, zip the PDF and all the attachments relating to the conversation into a single Zip file, and download it.
I've got the zip and download part working - all I'm missing at this stage is the ability to render the ascx control as HTML (or a stream), which contains all the data related to the conversation, to feed into the PDF library.
I'm still looking at various HTML to PDF libraries to find one that is reasonably priced (or OSS) and capable of handling HTML to PDF.
Thanx, Alon
Well, one way to do this is to use a client-side API call to pass the output HTML to the PDF generation code. If the PDF tooling you're using doesn't support this, you could easily do this for yourself by creating a web API endpoint in your custom module(s) to accept a chunk of HTML and then use that to generate the desired PDF.
Is there a way to get the rendered HTML from a control .ascx without setting that control to be the current one?
I've tried calling LoadControl(controlPath) which returns an instance of the control, and when I rey return the rendered HTML - none of the events inside the control had fired (like Page_Load) so none of the data used within the control was available.
So, I 'hacked' this a bit in order to get this resolved.
The issue I had is that when I loaded the control.ascx - it didn't fire any of the internal events (like Page_Load) etc. Even when I created a new Page object and loaded the control into the page - the events weren't firing. As a result - none of the data was loaded and the control rendered without any of the actual data being present.
So I haved it - I created a method on my control (called ForceLoadData), and just before I render the page / control using RenderControl(htmlWriter), I call ForceLoadData which in turn calls the Page_Load event with all the code to load the data etc.
While it is a bit of a hack - it seems to be working nicely.
Yeah, it gets a bit tricky to load a control dynamically with all of it's events. It's a bit more code to make it all load properly. However, that would have been more of a "hack" then what you did, maybe. The most straightforward approach (in my opinion) would have been my original suggestion. But, if it works, that's all that matters now. 😎 🙌🏽
These Forums are for the discussion of the open source CMS DNN platform and ecosystem.
For the benefit of the community and to protect the integrity of the ecosystem, please observe the following posting guidelines:
Awesome! Simply post in the forums using the link below and we'll get you started.