I've been trying to get my HTML to accurately translate into a PDF for some time now but I can't see what I'm doing wrong.
Here's my code for the page:
Imports HiQPdf
Imports System.Text
Imports System.IO
Imports System.Web.UI
Partial Class MODULES_CostCalculator_CostCalculator
Inherits System.Web.UI.Page
Dim convertToPdf As Boolean = False
Protected Sub printClick()
convertToPdf = True
End Sub
Protected Overrides Sub Render(writer As System.Web.UI.HtmlTextWriter)
If (convertToPdf) Then
System.Diagnostics.Debug.Write("overriding render")
Dim tw As TextWriter = New StringWriter()
Dim htw As HtmlTextWriter = New HtmlTextWriter(tw)
'render the html markup into the TextWriter
MyBase.Render(htw)
'get the current page html code
Dim htmlCode As String = tw.ToString()
System.Diagnostics.Debug.Write(htmlCode)
'convert the html to PDF
'create html to pdf converter
Dim htmlToPdfConv As HtmlToPdf = New HtmlToPdf()
'htmlToPdfConv.MediaType = "print"
'base url used to resolve images, css and script files
Dim currentPageUrl As String = HttpContext.Current.Request.Url.AbsoluteUri
'convert html to a pdf memory buffer
Dim pdfBuffer As Byte() = htmlToPdfConv.ConvertHtmlToMemory(htmlCode, currentPageUrl)
'inform the browser about the binary data format
HttpContext.Current.Response.AddHeader("Content-Type", "application/pdf")
'let the browser know how to open the pdf doc
HttpContext.Current.Response.AddHeader("Content-Disposition",
String.Format("attachment; filename=ConvertThisHtmlWithState.pdf; size={0}",
pdfBuffer.Length.ToString()))
'write the pdf buffer to http response
HttpContext.Current.Response.BinaryWrite(pdfBuffer)
'call End() method of http response to stop ASP.NET page processing
HttpContext.Current.Response.End()
Else
MyBase.Render(writer)
End If
End Sub
Does anyone see what I might be doing wrong? A lot of the HTML is linked to a Knockout ViewModel, so I'm not sure if that would be causing an issue.
To be clear, I can create PDF's of the page, but only with the HTML in the state it was when the page first loaded. If I change any of the data-bound HTML, it doesn't reflect when I try to make another PDF.
Priorplease try:
Adding clear:
Response.Clear()
Response.ClearHeaders()
After MyBase.Reder method
htw.Flush()
At the before Response.End
Response.Flush()
If nothing above works:
Call support :)
I think the problem is that you're changing the state of the page after it has rendered (using JavaScript), and you're expecting this: -
MyBase.Render(htw)
'get the current page html code
to give you the current state of the page. It won't - it will give you the state of the page as it was rendered. If you're using Knockout or anything other scripting to manipulate the DOM after the page has loaded, the server-side model of the page knows nothing of these changes.
Related
I am trying to access the HTML within two iframes using Selenium Basic in VBA, as IE has been blocked on our machines, and Python, etc. are not available to us.
Previously I could access the html with this:
Dim IE As InternetExplorerMedium
Set IE = New InternetExplorerMedium
' actual website excluded as it is a work hosted website which requires login, etc.
website = "..."
IE.navigate (website)
Dim IEDocument As HTMLDocument
Set IEDocument = IE.document.getElementById(id1).contentDocument.getElementById(id2).contentDocument
From there I would have access to all the HTML elements which I could work with.
Now I am trying the following with Selenium Basic:
Set cd = New Selenium.ChromeDriver
website = "..."
cd.Start baseUrl:=website
cd.Get "/"
Dim af1 As Selenium.WebElement, af2 As Selenium.WebElement
Set af1 = cd.FindElementById("CRMApplicationFrame")
Set af2 = af1.FindElementById("WorkAreaFrame1")
It works up to the last line, as it is able to set af to the "CRMApplicationFrame" id; however, I am unable to get inside of it.
I think the solution lies in executing a bit of JavaScript, similar to as in this video:
https://www.youtube.com/watch?v=phYGCGXGtEw
Although I don't have a #ShadowDOM line, I do have a #document line.
Based on and trying to adapt the video I have tried the following:
Set af2 = cd.ExecuteScript(Script:="return arguments[0].contentDocument", arguments:=af1 )
However, that did not work.
I also tested:
Dim af1 As Selenium.WebElement
Set af1 = cd.FindElementById("CRMApplicationFrame")
call cd.SwitchToFrame (af1)
Debug.Print cd.PageSource
However, the SwitchToFrame line won't execute, with a 438 error: Object doesn't support this property or method.
Any advice or guidance on how I could succeed would be highly appreciated!
Replace:
call cd.SwitchToFrame (af1)
with:
cd.SwitchToFrame "CRMApplicationFrame"
You can find a relevant detailed discussion in Selenium VBA Excel - problem clicking a link within an iframe
How do I save the current form to an image in vb.net without taking a screenshot. I would like the user to have the option to use a save file dialog to choose their file name, and if necessary I will resort to taking a screenshot, but I would prefer not to.
So far I am using CefSharp in WinForms and passing a private class to the javascript/html side of my program. When the user clicks on an html button to save an image this calls a method inside of the aforementioned class which should open up a save file dialog to draw the image.
I've tried methods that include taking a screenshot, but for obvious reasons, I cannot use a save file dialog because that hides the frame I am trying to take an image of. Maybe there is a work around to this that I am not seeing.
I have tried other methods that involve creating a Graphics object, but they always return a blank image. I saw a post somewhere saying not to use the CreateGraphics() function, but instead to use a picture box event. I am not sure how to properly call a picture box that exists within the current form from a private class(The class which is passed to javascript I have defined as a private class in the same .vb file as my form's vb code)
Public Sub saveImage(dataURL As String, format As String)
Dim t As New Thread(Sub()
Dim sfd As New SaveFileDialog()
sfd.Filter = "Png Image|*.png|Jpeg Image|*.jpeg| Jpg Image|*.jpg"
sfd.Filter = format + " Image|" + "*." + format.ToLower()
sfd.Title = "Save Plot Image."
sfd.FilterIndex = 2
If sfd.ShowDialog() = DialogResult.OK Then
' Dim array As String() = dataURL.Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)
' Dim byteArray As Byte() = Convert.FromBase64String(array.GetValue(1))
' Dim ms As MemoryStream = New MemoryStream(byteArray)
' Dim image = System.Drawing.Image.FromStream(ms)
' Image.Save(sfd.FileName)
Dim bm As Bitmap = New Bitmap(frmGraph.Width, frmGraph.Height)
Dim rect As Rectangle = New Rectangle(0, 0, frmGraph.Width, frmGraph.Height)
frmGraph.DrawToBitmap(bm, rect)
bm.Save(sfd.FileName, ImageFormat.Png)
End If
End Sub)
t.SetApartmentState(ApartmentState.STA)
t.Start()
t.Join()
The code I have defined above is a public sub within a private class. The code saves an image file, but the file is blank.
Hi I am trying to open HTML file in Firefox browser with following code
Dim NewProcess As Process
NewProcess.Start(url1)
by default it is opening in Firefox as my default browser is Firefox but it is opening with all tools and menu bar how can I open it without tools and menu.
how Can I use
window.open(url,"MyWindow","config='toolbar=no, menubar=no,scrollbars=no,resizable=no,location=no,directories=no,atus=no'");
propery to this page
You can try passing the -url parameter along with that code of yours when opening a new Firefox process.
Dim NewProcess As Process = Process.Start("firefox.exe", "-url ""javascript:window.open('" & url1 & "','MyWindow','config=toolbar=no,menubar=no,scrollbars=no,resizable=no,location=no,directories=no,atus=no');""")
EDIT:
Alternatively (since this does not seem to work in all cases) you could write a temporary HTML file which when opened creates a new window per your specifications.
Public Const BaseWindHTML As String = "<script>window.open('<replaceurl>','MyWindow','config=toolbar=no,menubar=no,scrollbars=no,resizable=no,location=no,directories=no,atus=no'); window.close();</script>"
Public Sub OpenNewWindow(ByVal URL As String)
Dim HTMLPath As String = IO.Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory(), "newwindow.html")
Using Writer As New IO.StreamWriter(HTMLPath, False)
Writer.Write(BaseWindHTML.Replace("<replaceurl>", URL))
End Using
Dim fProcess As Process = Process.Start("firefox.exe", "-url """ & HTMLPath & """")
fProcess.WaitForInputIdle()
Threading.Thread.Sleep(1500)
fProcess.CloseMainWindow()
End Sub
Example use:
OpenNewWindow("http://www.google.com/")
I want to use global resource in my javascript code. Normally, when the code is inside the ASP code, I use
<%=GetGlobalResourceObject("Resource", "MONTHS_SHORT1") %>
and it works. But, when the javascript code is not inside the ASP code, for example in a folder as calendar.js, it does not work. How can I use the global resource in this case? Note: The resource file is inside my App_GlobalResources folder.
Look at the following post I created describing how you can serialise global and local resources and retrieve them from javascript using jQuery and JSON.
http://bloggingdotnet.blogspot.com/2010_02_01_archive.html
Firstly, create a new handler (.ashx) file. This one was written quite some time ago, so uses vb.net and custom JSON serialisation:
Imports System.Web
Imports System.Web.Services
Imports System.Xml
Imports System.Resources
Imports System.Reflection
Public Class Localisation
Implements System.Web.IHttpHandler
Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
Dim files As String = context.Request.QueryString("files")
Dim local As String = context.Request.QueryString("local")
Dim isLocal As Boolean
Dim folder As String = "App_GlobalResources"
context.Response.ContentType = "text/javascript"
'Write out file as object
context.Response.Write("{")
'Determine if local resource file
If local IsNot Nothing Then
isLocal = CBool(local)
If isLocal Then folder = "App_LocalResources"
End If
If files Is Nothing OrElse files.Length = 0 Then Throw New ArgumentException("Parameter 'files' was not provided in querystring.")
Dim flag As Boolean = False
For Each file As String In files.Split(",")
If flag Then context.Response.Write(",")
Dim className As String = file.Split(".")(0)
'Write the class (name of the without any extensions) as the object
context.Response.Write(className)
context.Response.Write(":{")
'Open the resx xml file
Dim filePath As String = context.Server.MapPath("~\" & folder & "\" & file)
Dim document As New XmlDocument()
Dim flag2 As Boolean = False
document.Load(filePath)
Dim nodes As XmlNodeList = document.SelectNodes("//data")
For Each node As XmlNode In nodes
'Write out the comma seperator
If flag2 Then context.Response.Write(",")
Dim attr As XmlAttribute = node.Attributes("name")
Dim resourceKey As String = attr.Value
context.Response.Write(resourceKey)
context.Response.Write(":""")
'Write either the local or global value
If isLocal Then
context.Response.Write(HttpContext.GetLocalResourceObject(String.Format("~/{0}", file.Replace(".resx", "")), resourceKey)) 'Has to be full path to the .aspx page
Else
context.Response.Write(HttpContext.GetGlobalResourceObject(className, resourceKey))
End If
context.Response.Write("""")
'Flag that we need a comma seperator
flag2 = True
Next
context.Response.Write("}")
flag = True
Next
'End file
context.Response.Write("}")
End Sub
Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
Get
Return True
End Get
End Property
End Class
When that is working, use the following jQuery code to make an ajax call to the http handler and return the contents of the resource file as an object literal.
// -- Localisation --
var localResources;
var globalResources;
//Sample JSON for javascript resource values eg {TrackDetail:{HideHelp:"Hide Help", ShowHelp:"Show Help"}}
//Usage e.g: alert(localResources.TrackDetail.HideHelp);
//Load Localisation values into variables so that they can be available on the client
//Note that these values are loaded asynchronously, the code in the function will not run until the call has completed.
$.getJSON('Localisation.ashx?files=TrackDetail.aspx.resx&local=true', function(data) { localResources = data});
$.getJSON('Localisation.ashx?files=Errors.resx,Strings.resx', function(data) { globalResources = data});
You would have to generate JavaScript code that mimics the resources you need, as ASP.NET runs server-side, and JavaScript runs client-side.
<script language="javascript" type="text/javascript">
var MONTHS_SHORT1 = <%=GetGlobalResourceObject("Resource", "MONTHS_SHORT1") %>;
</script>
I often use a hidden literal to hold the resource text then grab the localised text using javascript and jquery:
<asp:Literal runat="server" ID="Literal1" visible="false" Text="<%$ Resources:String, MyResourceText%>" />
<input type="button" id="Button1" value="" />
<script type="text/javascript">
$(document).ready(function () {
// Update the buton text with the correct localised lookup
var ButtonText = '<%=Literal1.Text%>';
$("#Button1").attr('value', ButtonText );
});
</script>
This will register resource key.
ClientScriptManager cs = Page.ClientScript;
String scriptRegVariables = string.Format("var resourcetext = '{0}'", Resources.Resource.keyText);
if (!cs.IsClientScriptBlockRegistered("RegVariablesScript"))
{
cs.RegisterClientScriptBlock(typeof(_Default), "RegVariablesScript", scriptRegVariables, true);
}
Now inside .js file you can directly use it.
e.g. Alert(resourcetext);
Add the code above on Page_Load of control or aspx page
I am trying to do pretty much the same, as is for example on Sourceforge. After a user creates some data, I generate a file and I want it to be offered to him after a page load. However, I know almost nothing about javascript and simple copy-paste of
<script type="text/javascript">
var download_url = "http://downloads.sourceforge.net/sourceforge/itextsharp/itextsharp-4.1.2-dll.zip?use_mirror=dfn";
function downloadURL() {
if (download_url.length != 0 && !jQuery.browser.msie) {
window.location.href = download_url;
}
}
jQuery(window).load(downloadURL);
</script>
is not enough. It is important for the user to download the file, so how to do that?
A question related to the previous is - where to store the file i created? Once while using the asp.net development server and then on the real IIS server? And how should this address look? When I tried
setTimeout("window.location.href='file://localhost/C:/Downloads/file.pdf'", 2000);
I was getting nothing, with HTTP an error of unknown address.
See Haack's DownloadResult example. It explains (I think) exactly what you're truing to do. Except you would provide the timeout call with your download action url.
you're asking the user's browser to look for a file on their own computer... that you're trying to save there.
you could use something like:
window.location.href='http://www.yourServer.com/generatePDF.asp?whatever=whatever'
where http://www.yourServer.com/generatePDF.asp?whatever=whatever is what is generating the pdf file for the user
On the server, you have to set the content disposition in the response header to "Attachment" as described in these answers.
If you do that, the download will not affect the page that is currently displayed in the browser. So, if you initiate a request in Javascript that gets an attachment, the browser will leave the page alone, and the user will see a message box with the Open/Save/Cancel question.
You can create your own PdfResult which extends ActionResult like this:
public class PdfResult : ActionResult
{
public byte[] Content { get; set; }
public string FileName { get; set; }
public override void ExecuteResult(ControllerContext context)
{
var response = context.HttpContext.Response;
response.AddHeader("content-disposition", "attachment; filename=" + this.FileName);
response.AddHeader("content-length", this.Content.Length.ToString());
response.ContentType = "application/pdf";
using (MemoryStream memoryStream = new MemoryStream(this.Content))
{
memoryStream.WriteTo(response.OutputStream);
}
response.End();
}
Then in your action you can simply return the file as follows:
public ActionResult Pdf(string param1...)
{
var content = GeneratePdf(); //etc
var fileName = AssignFileName();
return new PdfResult { Content = content, FileName = fileName + ".pdf" };
}
A couple of different things. First, since you are using MVC, create an action that actually generates the file and returns it as a FileResult. The file can be an actual file on the server, but it can also be generated dynamically -- say in a MemoryStream -- and the FileResult created from that. Set the content to application/octet-stream or the actual file type if it's not one that will render in the browser via a plugin. Second, don't generate the file in the action that renders the page, but rather call the action that generates the FileResult from that page using the technique you've referenced (though it looks like they are doing something different for IE). If the MIME type is not one that can be rendered it will be downloaded.
public ActionResult GenerateFile( string value, int other )
{
MemoryStream file = new MemoryStream();
...
return File( file, "application/octet-stream" );
}