I am trying to find a tool that can be used to create PDFs from websites. These websites all have Bootstrap based client side view settings, such as tabs, toggles, and paging. As such, there is no post-back to the server. I need to be able to create a pdf that is in the same state as the user sees it.
In my research, I have only been able to find tools that can create PDFs if given a URL, or if given a HTML string. Examples of these tools include ActivePDF's Webgrabber, and EVO PDF. However, they are not able to generate the PDF's with the client-specific settings, but instead only see the default selections of a given page. It is not possible for me to do a post-back to the server, so I am looking for a tool that can create PDFs on the fly, with the dynamic settings intact. I am working in ASP.NET, and so I would like a tool that is .NET friendly as well. Lastly, I would prefer the tool to not be open-sourced.
It sounds like a proper solution here would be to take data from your client side, post it via AJAX (not an ASP.NET postback!) and then process it on the server side to generate the PDF. Since you haven't given much detail, I'm going to assume we don't need to send over all the HTML, but rather values that were entered via a form.
<script>
function postDataToServer(){
var itemId = $("#ItemIdTB").val();
var quantityOrdered = $("#QuantityTB").val();
$.ajax({
url : "GeneratePdfFromForm?itemId="+itemId+"&quantityOrdered="+quantityOrdered
})
.done(function(){alert("Success!");})
.fail(function(){alert("Failure!");});
}
</script>
My answer is using jQuery, which is a JavaScript library that simplifies AJAX and DOM manipulation. However, this technique is doable with other JavaScript libraries (or even without a library).
On the server side, let's have an ASP.NET Web API function that can handle that AJAX Post.
[Route("GeneratePdfFromForm")]
public static void GeneratePdfFromForm(string itemId, int quantityOrdered)
{
Debug.WriteLine("Received itemId {0} and quantity {1}", itemId, quantityOrdered);
byte[] pdf = GeneratePDF(itemId, quantityOdered); //you'll need a function called GeneratePDF that can generate your PDF based on the parameters, probably using a library like iTextSharp.
//now do you want with the PDF byte array
}
So that's how you'd generate the PDF. You could return it from the GeneratePdfFromForm() function back to the client. But since that's potentially a long running task, you should probably implement it in the background using something like Hangfire, then when the PDF is ready you'd present it to the client for download (perhaps using SignalR or jQuery AJAX polling to alert the client of when the PDF is ready).
I was passing data from the client side to the server side via query string. You could instead create a class to represent the parameters for your PDF generation, then pass that form the client side to the server side via jQuery AJAX's data parameter.
If you really want to post the entire HTML from the client side to the server, you could so something like this:
var html=$("html").html();
$.ajax({
url: <your url here>,
data: html,
contentType: "text/html"
});
However, I'm willing to bet the actual HTML isn't what you care about for generating the PDF, but rather the selected values from some client side form. The actual HTML would include everything on the client side, including navigation menus, scripts etc. You could post a subset of the HTML using a different selector (ex: $("#OrderDiv").html()), which is a technique some people use to generate PDF's. But I think it's much cleaner to decouple your HTML intended for the browser from the way the PDF is generated, so that changes to your site don't mess up the PDF. You can then use the PDF creating library's capabilities to build the PDF rather than using HTML.
Related
Im using Visual Studio 2012 and creating a web page using "ASP.NET Web Site (Razor v2)"
Im using Java to generate a random link;
<script>
var random = new Array();
random[0] = "example1.com";
random[1] = "pattern1.com";
random[2] = "specimen1.com";
</script>
<script>
function randomlink() {
window.location = random[Math.floor(Math.random() * random.length)];
}
</script>
A Random URL
When I click the A Random URL link it opens a random page from the list in the script above. I'ts all good, but because its a verry big list I need a way to do the same without having it in HTML because its slowing the loading of the page since its in the _SiteLayout.cshtml. Thanks.
Among your choices are the following options:
Send all the URLs to the client and have the client pick a random choice.
Have the server pre-pick the random URL and send only that one to the client (it can just be put directly into the <a> link. No need for javascript at all.
Make an ajax call to the server to request a random link and when that is returned, go to it.
Make a get request to the server and have the server return a redirect to a randomly chosen URL.
It sounds like you don't want to implement the first option if you have a zillion URLs.
The second option is probably the easiest as it requires only slightly modifying the generation of the page and requires no new server APIs. You just have to figure out how to select a random URL in your server-side environment.
The third and fourth options are the least efficient as they require a call to the server, a response from the server with the new URL and then a client redirect to the actual URL.
I would pass the random url with the page when it renders from the server. You can generate the url on the server using c#'s Random class.
A Random URL
Just pass a model that you reference in your view.
In reference to this article:
http://msdn.microsoft.com/en-us/library/ms252072(VS.80).aspx
I can access my SSRS report from my Web Application but the problem is it does not accept the parameters in the URL.
here is the format that it accepts
Report Server URL:
http://ABCD.com/reportserver
Report Path:
/Company works/Sales report/ABC
Can anyone help me?
I want to pass parameters in the SSRS url through report viewer.
You can not pass parameters via the URL since the control is expecting on the URL to the actual reportserver and nothing more.
Example:
serverReport.ReportServerUrl = new Uri("http://ABCS.com/reportserver");
serverReport.ReportPath = "/AdventureWorks Sample Reports/My Report";
You can pass these parameters in your code behind though by using the ReportViewer.ServerReport.SetParameters() function. Here is a snippet of code I used for this purpose:
var parametersCollection = new List<ReportParameter>();
//add generic parameters that every report needs
parametersCollection.Add(new ReportParameter("RandomizerString", new Random().Next().ToString(), false));
parametersCollection.Add(new ReportParameter("MachineName", Request.UserHostAddress.ToString(), false));
ERDashboard.ServerReport.SetParameters(parametersCollection);
can't seem to add a comment to Sam's answer so I'll post another:
The ReportViewerControl ReportServerURL property is a lot different than formatting a general URL to access a report. You could use the logic that sam has provided to navigate a web browser control to that URL which would also render the report within your web app. It's another to try if you want.
As a note there are various reasons that you may choose a webbrowser control vs a reportviewer control. The reportviewer control is a lot easier to manage in the code-behind, but there is a memory issue when refreshing a report for extended periods of time (lots of information through google about memory leaks). This is the scenario where I have instead used the webbrowser and manually formatted URLs to pass parameters instead.
I need to implement single page applications using ASP Web Forms. I faced with a navigation problem. I need to use a navigation pattern like this:
http:// web site url / ... / page.aspx? {query string} # {ListId} / {ItemId}
When a user request a data from the server, the request on the server doesn't contain hash # (because this is a client-side feature). And it looks like this:
http:// web site url / ... / page.aspx? {query string}
So, actually I need two requests:
to get a page without hash and load javascript;
to handle hash data using javascript and async call required data from the server.
Is it possible to implement this logic with only one request?
Are there any best practices?
You can append ListId/ItemId to query string before sending request and read it regularly on a server.
var url = 'http://example.com?param1=10¶m2=20#1000';
var beforeHash = url.split('#')[0];
var itemId= url.split('#')[1];
var processedUrl = beforeHash + '&itemId=' + itemId;
If your request is not already fired from JavaScript, you will have to hook into link's click event...
Or maybe you can get rid of # entirely and scroll content via JavaScript (my guess is that you use # because of local anchors to jump to different places in document)?
BTW There is window.location.hash property.
Update:
Based on your comment the flow is like this:
User types URL with #ItemId
Server returns the page
JavaScript reads #ItemId from window.location, puts it into QueryString and makes a request
Server returns the page based on modified QueryString
In this situation the two-requests pattern seems to be the only viable option. By design server does not get #Item part (called fragment). So there is no way to guess ItemId upon initial request. If after second (ajax) request, you refresh #ItemId dependant parts of the page through JavaScirpt, user experience will not be hindered much.
The problem here is the middle of the line (HTML).
The chain:
I have WinForm program that uses awesomium (alternative to native webBrowser) to view Html page that has a part of asp.net page in it's iframe.
The problem:
The problem is that I need to pass value to asp.net page, it is easily achieved without middle of the chain (Html iframe) by sending hashed and crypted querystring.
How it works:
WinForm do some thing, then use few-step-crypt to code all the needed values into 1 string.
Then it should send this string to asp.net page through the iframe (and that's the problem, it is easy to receive query string in asp.net page, but firstly I need to receive it in Html and send to asp.net).
Acceptable answers:
1) Probably the most easily one - using JavaScript. I have heard it is possible to be done in that way.
How I imagine this - I send query string from WinForm to Html page as http:\\HtmlPage.html?AspNet.aspx?CryptedString
Then Html receive it with JavaScript and put querystring "AspNet.aspx?CryptedString" into iframe's "src=http:\\" resulting in "src=http:\\AspNet.aspx?CryptedString"
And then I easily get it in asp.net page.
2) Somehow create >>>VIRTUAL<<<(NOTE: Virtual, I don't want querystring to be saved on the HDD, even don't suggest) asp.net or html page with iframe source taken directly from WinForm string.
Probably that is possible with awesomium, but I'm new to it and don't know how to (if it is possible ofc).
3) Some web service with which I can communicate between asp.net and WinForm through the existing HTML iframe.
4) Another way that replace one of 3 previous, that doesn't save "values" in querystring/else on HDD nor is visible for the user, doesn't use asp.net page's server to create iframe-page on it. On HTML page's server HTML is only allowed, PhP isn't.
5) If you don't know any of 4 above - suggest free PhP hosting without ads (if such exists, what I highly doubt).
Priority:
The best one would be #3, then #2, then #1, then #5 (#4 is excluded as it is unknown).
And in the end:
Thanks in advance for your help.
P.S.Currently at work, so I'll check/try all answers later on and will report tomorrow if any suits my needs. Thanks again.
Answering my own question. I have found 2 ways that can do what I did want.
The first one:
Creating a RAM file System.IO.MemoryStream or another method (google c# create a file in ram).
The second one:
Creating a hidden+encrypted+system+custom-readable-only-by-program-crypt file somewhere in the far away folder via File.SetAttributes Method and System.IO.StreamWriter/Reader or System.IO.FileStream or System.IO.TextWriter, etc. depending on what it should be.
Once this file was used for needs delete it + delete on exit + delete on start using
if (File.Exists(path)
{
File.Delete(path);
}
(Need more reputation to post few links -_-, and I don't want to post only part of them, either all or no at all, so use google if you'll need anything from here).
If you'll need to store "Small temp file" and not for a long time use first one, if "Heavy" use second one, unless you badly need to use RAM for it.
Current situation
I have ASP.NET web application that render PDF for users using MS Report Viewer. The PDF is rendered with this method:
Byte pdfByte = Byte();
pdfByte = ReportViewer.LocalReport.Render("PDF", Nothing, mimeType, encoding, extensions, stream, warning)
And send to browser as an attachment with response object:
Response.Clear()
Response.ContentType = mimeType
Response.AddHeader("content-disposition", "attachment; filename=myfile." + extension)
Response.BinaryWrite(pdfByte)
Response.Flush()
Response.End()
This work great! The user browser will get the PDF as download-able attachment.
What I am trying to achieve
Render multiple PDF and send all of them separately to user's browser. User will get separate PDF documents. It doesn't matter whether they will get them all at once or one by one.
The problem
The problem is after Response.End() the next line of code is not executed. I have tried to store the pdfByte object in session, looping through it and send them to user's browser with Response object but after the first PDF get sent then it stop.
I have also tried removing Response.End() thinking the code will keep running but still it stop after the first PDF get sent.
Please advice any workaround or tips. Thanks!
You cannot send multiple files (as separate entities) in a single HTTP response (the protocol does not support it. However, what you can do is to archive all files together and send the that single zip (or whatever format you want) to the client.
You can use libraries such as DotNetZip/SharpZipLib to combine (and compress) files together. Based on library API, you may need to save PDF files to disk before adding to zip file. Also do not forget to change your content type appropriate while sending the zip file to client.
Yet another alternative is to provide user with a page having multiple links to download files. It may mean that you either have to store your PDFs for some time so that they can served later (via links) or make link point to a handler that will re-run the report again to get the PDF out of it.
Admittedly the method I'm using doesn't feel very elegant, but here's what I'm doing:
create one IFRAME on the page for each document you want to send to the client (maybe create the IFRAMEs dynamically in server-side code if the number of documents is variable);
create a HttpHandler that generates the PDF documents, depending on a parameter you're passing in through the QueryString, just like you're doing above;
set the src on all IFRAMES to the URL of the HttpHandler with the appropriate parameters attached.
Of course the HttpHandler needs to do implement security logic, if required.
This works quite beautifully: If I want to send 3 documents, I create 3 IFRAMEs, set their src, and the user will see 3 "Save As..." dialogs pop up.