I'm trying to generate a PDF file with HTML content that's being sent via AJAX from JavaScript.
I have the HTML being sent properly, and the PDF being generated correctly using the HTML, but I'm stuck trying to figure out how to initiate the file download on the user's browser.
This is what I'm currently trying. This works in a IHttpHandler file when hitting the URL for the file directly, but doesn't work in the ApiController when posting via AJAX.
I've also tried posting to the IHttpHandler file with the same results. Everything works fine until I try to initiate the download by using BinaryWrite.
public class DownloadScheduleController : ApiController
{
public void Post(HtmlModel data)
{
var htmlContent = data.html;
var pdfBytes = (new NReco.PdfGenerator.HtmlToPdfConverter()).GeneratePdf(htmlContent);
using (var mstream = new System.IO.MemoryStream())
{
HttpContext.Current.Response.ContentType = "application/pdf";
HttpContext.Current.Response.AppendHeader("content-disposition", "attachment; filename=UserSchedule.pdf");
HttpContext.Current.Response.BinaryWrite(pdfBytes);
HttpContext.Current.Response.End();
}
}
}
Here is the Ajax request. It's using Angular's $http.
$http({
method: 'POST',
url: 'api/downloadSchedule',
dataType: "json",
contentType: "application/json; charset=utf-8",
data: data
});
Alternatively I could return the PDF binary to JavaScript, but then how do I use JavaScript to save the pdf?
Any help will be greatly appreciated.
For security reasons, the browser cannot initiate a file download from an AJAX request. It can only be done by navigating to a page that sends a file.
Usually if you need to initiate a download from Javascript, you would either set window.location.href = urlToFile, or create a new iframe pointing to that url.
In your case this would not serve, because the above methods only perform a GET request, and you need a POST, so I only see two possible solutions to this:
you could modify your JS to - instead of submitting the request with $http - create an HTML form with fields that correspond to what you originally posted with your AJAX request, then append the form to the page, populate the fields and submit the form
or you could change your server-side code as well as your Javascript.
If you opt for the second solution, you could follow the approach of using two methods on the server side:
one POST that generates the file and saves it in the cache (there are probably better solutions that using the cache, especially if you're on a server farm, but let's keep it simple)
one GET that that retrieves the cached content and returns it to the user. In this way you will be able to post the data via an AJAX call, and then getting the file by navigating to the right url.
Your C# code would look something like this:
public class DownloadScheduleController : ApiController
{
public object Post(HtmlModel data)
{
var htmlContent = data.html;
var pdfBytes = (new NReco.PdfGenerator.HtmlToPdfConverter()).GeneratePdf(htmlContent);
var policy = new CacheItemPolicy { AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(30), Priority = CacheItemPriority.NotRemovable };
var cacheId = Guid.NewGuid().ToString();
MemoryCache.Default.Add("pdfBytes_" + cacheId, pdfBytes, policy);
return new { id = cacheId };
}
public void Get(string id)
{
var pdfBytes = MemoryCache.Default.Get("pdfBytes_" + id);
MemoryCache.Default.Remove("pdfBytes_" + id);
using (var mstream = new System.IO.MemoryStream())
{
HttpContext.Current.Response.ContentType = "application/pdf";
HttpContext.Current.Response.AppendHeader("content-disposition", "attachment; filename=UserSchedule.pdf");
HttpContext.Current.Response.BinaryWrite(pdfBytes);
HttpContext.Current.Response.End();
}
}
}
Your frontend could then be something like:
$http({
method: 'POST',
url: 'api/downloadSchedule',
dataType: "json",
contentType: "application/json; charset=utf-8",
data: data
}).success(function(response) {
// note: the url path might be different depending on your route configuration
window.location.href = 'api/downloadSchedule/' + response.id;
});
Keep in mind that my code is just meant to show you the approach, it's not meant to be used as it is in production. For example, you should definitely clear the cache immediately after use. You should also make sanity checks in your Get method for what is retrieved from the cache, before using it. Also, you might want to take additional security precautions on the data that you store and retrieve, depending on your requirements.
Related
I'm making a simple server to send data to a .json file and receive that data from another page but I have problem how to store data in .json file
I used following code but it didn't work
<script src="jquery/jquery-3.4.1.min.js"></script>
<script>
var _lname = "x";
var _fname = "y";
var _mname = "x";
$.ajax({
type: "POST",
url: "data.json",
data: "{'lastName':'" + _lname + "','firstName':'" + _fname + "','middleName':'" + _mname + "'}",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function () {
}
});
</script>
Simply POSTing data to a JSON file won't work, as sending a POST request requires the server to listen for requests and do something with the payload you sent. You could make a simple NodeJS or PHP script (or any other server-side language for that matter) that could handle saving the payload to a JSON-file.
When you make a POST request, you send data to a web server.
The web server can do something with the data in the POST request.
By default, it does nothing. Imagine the problems that would be caused if anybody could make a POST request to anyone else's web server and write a new file to it. Google's homepage would be defaced every other second.
If you want to store the results of a POST request, then you need to write server-side code to do it (and you almost certainly will want to perform authentication and authorisation when you do so).
Note that the value of data: in your example code will never be valid JSON. Don't try to write JSON by mashing strings together. Use a library function like JSON.stringify.
Can you please let me know how I can get a ZIP file stored on same server using the jquery Ajax? Please be informed that I do not want to download the file
I need to pass the result , if success? to an API snippet like this, (this is using a Form to pass a zip file from client to the request Here is The Working Demo
request({
url: portalUrl + '/sharing/rest/content/features/generate',
content: myContent,
form: dom.byId('uploadForm'),
handleAs: 'json',
load: lang.hitch(this, function (response) {
if (response.error) {
errorHandler(response.error);
return;
}
var layerName = response.featureCollection.layers[0].layerDefinition.name;
addShapefileToMap(response.featureCollection);
}),
error: lang.hitch(this, errorHandler)
});
but I need to pass the zip file from server witout using a form and here is what I would like to do
var data = "www.mydomain.com/GIS/App.ZIP";
request({
....,
form: data,
....
});
Update
As menitoned API offers the Formdata option as well but how I can pass second parameter of type inside the append method?
var theFile = "http://localhost/Portal/APP.ZIP";
var myFormData = new FormData();
myFormData.append(theFile, ? );
Javascript cannot access the local filesystem without user intervention for security reasons. A user must take an action to load a file. Otherwise, it would be very easy for malicious web pages to traverse your file system.
You can use javascript via AJAX to trigger a server side script which can access the server file system and return results to javascript though.
So here's my situation...
We have an on-prem installation of Microsoft Dynamics CRM and I am trying to make an ajax call from it to a service I created on another one of our servers. There have been many issues already that I've solved - but I'm able at this point to successfully make a GET request to my service from CRM via javascript I've put on a form in CRM.
Just for reference (because I'm not entirely sure at this point if these things are related or not)...
I had to set anonymous authentication in IIS for my service (CRM has
its own authentication that I will be relying on)
I had to set a response header of Access-Control-Allow-Origin with the host address of our CRM installation
So, after doing those things I was able to successfully call my web service via GET. I could return back a string I had from a [HttpGet] web method.
But, now I need to actually call a web method via POST to post some data to my web service. So, below you can see my implementation for the service as well as the javascript I'm using the make the POST call.
using CRMService.Models;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Net.Mail;
using System.Web;
using System.Web.Http;
namespace CRMService.Controllers
{
public class DefaultController : ApiController
{
// GET: Default
public string Get()
{
return "Hi";
}
[HttpPost]
public string GiveParameters(TestClass tc)
{
try
{
Dictionary<string, object> Parameters = new Dictionary<string, object>();
Parameters.Add("id", tc.id);
Parameters.Add("one", tc.one);
Parameters.Add("two", tc.two);
NonQuery("InsertTestItem", ConfigurationManager.ConnectionStrings["TestConnection"].ToString(), Parameters);
return "success";
}
catch (Exception ex)
{
return "ex";
}
}
}
}
var new_budget = Xrm.Page.data.entity.attributes.get("new_budget").getValue();
var new_name = Xrm.Page.data.entity.attributes.get("new_name").getValue();
var id = Xrm.Page.data.entity.getId();
data = '{"TestClass":{"one":"' + new_name + '", "two":"'+ new_budget +'", "id":"'+ id +'"}}'
$.ajax({
type: "POST",
contentType: "application/json; charset=utf-8",
url: "https://<hiddingMyUrl>/api/Default/GiveParameters",
data: data,
dataType: "json",
success: function(data) {
console.log("Success");
},
error: function(result) {
console.log("Error");
}
});
When I make this POST call, at first I could see it was doing some "preflight" stuff and making an OPTIONS request - then returning a 403 (I think, if memory serves me right). I looked that up and solved that issue by adding a Access-Control-Allow-Headers header to my web service in IIS with the value of Origin, X-Requested-With, Content-Type, Accept
After doing that my POST actually gives a 200 status code - but, as you can see in my code, I should then be seeing data in a database if everything went well.
..So of course then the question is... is my web service code working properly? And normally I could test for that easily - however I am fairly new to web api. I don't really get the best way to testing at this point - and I don't know if it's something with my code specifically or if there is some configuration issue with web api itself.
Here is my routeconfig:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { action = "Index", id = UrlParameter.Optional }
);
}
You should try working with a REST client.
Here are two nice ones :
Advanced Rest Client
Postman
I personally prefer Postman but really both are good.
Im working in an application that uses java + spring on the server side aswell as extdirectspring. On the client side it uses extjs/javascript.
I want to poke a method on the serverside and retrieve a file from the database. If the file doesn't exist then I'd like to display an error in some way.
The attempt to retrieve the file and the check it exists need to happen in the same call - the file could be deleted in between calls.
The way I can see people have done it in the current application is using spring controllers + request mappings and a window.open("someUrl/filename.blah"); with the server returning the file from the mapped method.
This doesnt seem to let you handle the case where the file doesn't exist though.
Ideally I'd just like to send some json back from the server which has the file data (possibly null) and sucess/failure. When I get the response I can then either show some information about the failure or open the file. Unfortunately I can't observe the current failure mode because something somewhere is caching the files - if I delete them from the database then they appear to still exist and you can still download them!
By 'open' I mean show the standard 'what do you want to do with this file open/save' dialog. I'm not trying to parse the file or do anything with it - I just want to serve it up to the browser/user.
Is there a way to do that without using a url and window.open? Eg some method that takes a blob of data and a file name or similar?
Update
The transfer of the data/json isn't the problem I'm trying to solve.
As I'm using extdirect I'll probably just do it like this:
public class SomeClass
{
#ExtDirectMethod
public AFile getFile(Long id) throws Exception
{
//do stuff
}
}
Then on the clientside you just do:
someClass.getFile(id, function(file){
if(file.found){
SomeHowGiveThisToTheUser(file.name,file.data); ????
return;
}
ReportCouldntFind(file.name);
});
The bit I dont know how to do is the give the file to the user.
Further update
I dont think it's possible to do this without blob urls or data uri's. Both of these are mentioned in this post. I haven't tried them out as we are having to support a browser that is too old for both techniques.
You are wanting to do some standard Ajax (Assuming you have jQuery available).
Something like:
$.getJSON( url, function( data ) {
if (data.success){
} else {
}
});
And on the server side, add code to return the expected JSON.
In extJS:
Ext.Ajax.request({
url : url,
method: 'GET',
headers: { 'Content-Type': 'application/json' },
params : params,
success: function (response) {
var jsonResp = Ext.util.JSON.decode(response.responseText);
if (response.success){
// do success stuff, like using response.fileData
} else {
// do fail stuff
}
failure: function (response) {
var jsonResp = Ext.util.JSON.decode(response.responseText);
// etc.
});
On the server in a Java servlet you could something like this (assumes apache commons file util):
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
InputStream in = new URL( "http://remote.file.url" ).openStream();
IOUtils.copy(in, response.getOutputStream());
}
Probably a more spring specific way to do it, but that gives you an idea.
For your case, you want to wrap the file contents in a JSON object that includes the success proeprty. For that, use the Java JSON jar: http://json.org/java/
Update: Finally understand what you are asking.
It finally occurred to me that you are asking how to handle the actual download.
There's a couple ways these days.
Take a look at this SO answer, this is using an iFrame: Download File Using Javascript/jQuery
There are also several fancy downloader components, including several for jQuery.
So you would do a two part process: check for the file availability using a standard ajax call, and then if response.success, use the downloader to serve the file to the user.
I have an MVC 3 web app that has an Action which I need to access from a webform app via javascript.
My action returns a Json result, which I have checked works just fine. However, when accessing it with javascript from another application, I can see it reaching my action, doing all the work, returning my data, but the jquery function then gives me an error 200 and no data.
Example:
My MVC Action:
public ActionResult GetData(int categoryId)
{
var listofSeries = new List<string>
{
"foo1",
"foo2",
"foo3"
};
return Json(listofSeries, JsonRequestBehavior.AllowGet);
}
This returns me: ["foo1","foo2","foo3"]
Normally, my javascript function is used to query a database and get data back for a Highcharts chart but I have the same issue with this kind of example.
<script type="text/javascript">
$(document).ready(function () {
var foo = getData(9);
});
function getData(categoryId) {
var results;
$.ajax({
type: 'POST',
url: 'http://localhost:xxxx/Home/GetData',
//contentType: "application/json; charset=utf-8;",
async: false,
data: { "categoryId": categoryId },
dataType: "json",
success: function (data) {
console.log("results: " + data);
results = data;
}
});
return results;
}
</script>
With this I get:
http://localhost:63418/Home/GetData?categoryId=9 200 OK
results: null
If I add the contentType, I can't even see the function running in the firebug console, all I see is results: null. When I remove it I do but I get the error above.
I've been told it might not be doable because of "Cross-site scripting". And to instead have my javascript function call a webservice (or handler but I haven't quite figured out how to make one of those yet) that will then call my action, return my data to the service who then returns the data to the javascript.
But now I'm getting a 415 Unsupported Media Type error message when trying to connect to my webservice.
My IWebService.cs:
[ServiceContract]
public interface IWebService
{
[OperationContract]
[WebInvoke(ResponseFormat = WebMessageFormat.Json, Method = "POST")]
void GetData(int mileId);
}
My WebService.svc:
public void GetData(int categoryId)
{
string url = "http://localhost:63418/Home/GetWoWData?categoryId=" + categoryId;
WebRequest wr = WebRequest.Create(url);
WebResponse response = wr.GetResponse();
}
I'm not returning anything yet, it won't even go in this function. Giving me the 415 error message.
If I change the contentType to "text/xml" I get an error 400.
I would really like to have this work without using the webservice, and figure why I get an error 200 OK when I can see the action running, but if that's not possible, any idea why the webservice isn't working?
Thanks.
string url = "http://localhost:63418/Home/GetWoWData?categoryId=" + categoryId;
WebRequest wr = WebRequest.Create(url);
wr.Credentials = CredentialCache.DefaultNetworkCredentials; // uses current windows user
var response = (HttpWebResponse)wr.GetResponse();
I only just remembered I had this post and found the solution a while ago. It was solved by adding the credentials (in my case the current windows user) and getting the response as an HttpWebResponse.
200 makes me think everything is working ok, but IE is using a cached result, i've run into this problem before with IE. try adding:
$.ajaxSetup({
// Disable caching of AJAX responses */
cache: false
});
this will make it append a random parameter, and will stop IE from being stupid.