|
Rank: Newbie Groups: Member
Joined: 10/6/2022 Posts: 9
|
Hello, I'm trialing your software and one of the features I'm looking to achieve is being able to post a view model with a url to pdf request. The method below gives an example of a current method used for posting the view model to a page that uses JavaScript to render a chart. Quote: [HttpPost, Route("basicareachart/")] public async Task<IActionResult> BasicAreaChartAsync([FromBody] Chart chart) { try { var qwwqd = chart; switch (chart.DataSourceType) { case "logEvents": // code block chart.ChartDataDatasets = await _appInsightsQueryRepo.GetChartsFromAppInsightsWithQuery(chart); break; }
return View(chart); } catch (Exception ex) { return BadRequest(ex.Message); } }
I need to achieve a simialr result when requesting a url to pdf, in order that I can re-produce the same chart using your headless browser. Please could you advise the best approach for this? I'm not sure whether it would be easier to work with your option for "Calling HtmlToPdfOptions.AddPostData" OR use MVC to PDF, many thanks
|
|
Rank: Administration Groups: Administration
Joined: 5/27/2007 Posts: 24,217
|
Hi, MVCToPDF should be the easiest way to do this. This requires only minimim code changes. You can follow these steps: 1. Follow steps here to initialize MVCToPDF in your application: https://www.essentialobjects.com/doc/pdf/web/mvc.html#begin2. Add EO.Pdf.Mvc.RenderAsPDF attribute to your action. So the code would become this:
Code: C#
[HttpPost, Route("basicareachart/")]
[EO.Pdf.Mvc.RenderAsPDF]
public async Task<IActionResult> BasicAreaChartAsync([FromBody] Chart chart)
{
....
}
This will automatically "redirect" the output of your action to PDF. So whatever that triggers this action would get PDF result instead of the original HTML result. For example, if the action is triggered by a browser, then the browser would get the PDF file instead of the original HTML. 3. Once step 2 is sucessful, you can change this "always PDF" logic into conditional if needed. For example, you may only want to return PDF if a specific query string parammeter exists. To do so you would need to add AutoConvert=false to your attribute and then call RenderAsPDF conditionally in your action:
Code: C#
[HttpPost, Route("basicareachart/")]
[EO.Pdf.Mvc.RenderAsPDF(AutoConvert=false)]
public async Task<IActionResult> BasicAreaChartAsync([FromBody] Chart chart)
{
if (some_condition_is_met())
MVCToPDF.RenderAsPDF();
....
}
Hope this helps. Please feel free to let us know if you run into any problems or still have any questions. Thanks!
|
|
Rank: Newbie Groups: Member
Joined: 10/6/2022 Posts: 9
|
Hi, many thanks for coming back. I did some more testing using MvcToPdf but I couldnt get it to work for my particular environment. Instead I have reverted to using EO.Pdf.HtmlToPdf.ConvertUrl() and this seems to work OK for me now. Using the HtmlToPdf option means I achieve more flexibility and control as to how and under what scenario I can generate the PDF, my requirements was being able to do either of the following: - Export to PDF on a user request by posting to a controller api method. - Export to PDF from a process triggered from a background service such as a cron job. I'm able to call the ConvertUrl method from anywhere within the app, I'm not tied to using MVC and controller methods alone so I beleive I have a solution now.
Code: C#
[HttpGet, Route("basicareachart/")]
public IActionResult BasicAreaChart()
{
return View();
}
// I tested this by calling the POST method from Swagger UI.
// The results returns a download link
[HttpPost, Route("downloadchartreport/")]
public async Task<ActionResult> DownloadChartReportPdf()
{
ViewData["Message"] = $"{this.Request.Scheme}://{this.Request.Host}{this.Request.PathBase}";
var scheme = this.Request.Scheme;
var host = this.Request.Host;
var pathBase = this.Request.PathBase;
var url = "http://" + host + "/chartexport/basicareachart";
// create memory stream to save PDF
using var pdfStream = new MemoryStream();
EO.Pdf.HtmlToPdf.ConvertUrl(url, pdfStream);
// Reset stream position
pdfStream.Seek(0, SeekOrigin.Begin);
return File(pdfStream.ToArray(), "application/pdf", "Test Report");
}
|
|
Rank: Administration Groups: Administration
Joined: 5/27/2007 Posts: 24,217
|
Yes. Thanks for sharing the code. That will work too.
Please feel free to let us know if you have any more questions.
|
|
Rank: Newbie Groups: Member
Joined: 10/6/2022 Posts: 9
|
Hi, one thing I'm still not sure how to do is sending the EO.Pdf.HtmlToPdf.ConvertUrl as a post request rather than a get request. Ideally I'd like to send it as a post and then pass in a model of properties as the [FromBody] request, is this possible? thanks
|
|
Rank: Administration Groups: Administration
Joined: 5/27/2007 Posts: 24,217
|
Hi, Yes. This is possible. Depending on the complexity of your request, you can use either one of the following methods. For simple request, you can use this method: https://www.essentialobjects.com/doc/pdf/htmltopdf/post.htmlFor more complex request, you can use an HttpToPdfSession object, you can find sample code here: https://www.essentialobjects.com/doc/eo.pdf.htmltopdfsession.runwebviewcallback_overload_1.htmlThe key to the second method is you would call the underlying WebView object's LoadRequest method to load the request directly into the WebView. Please let us know if you still have any questions. Thanks!
|
|
Rank: Newbie Groups: Member
Joined: 10/6/2022 Posts: 9
|
Hi, I did review the documentaiton for "https://www.essentialobjects.com/doc/pdf/htmltopdf/post.html" but I wasnt able to add a model of properties without calling .ToString() method, I also tried to serialize the model object but for some reason it kept faling for me. I'll have another look. I kept getting the following text in the output pdf: {"type":"https://tools.ietf.org/html/rfc7231#section-6.5.13","title":"Unsupported Media Type","status":415,"traceId":"00- 2c374705c4a632b80b330707c3bf3362-a5f88e6b79c7132e-00"} Below is my sample code when trying to post the data:
Code: C#
private string? ChartRenderUrl { get; set; }
[BindProperty(SupportsGet = true)]
private ChartRenderViewModel? ChartRenderViewModel { get; set; }
[HttpPost, Route("basicareachart/")]
public IActionResult BasicAreaChartAsync([FromBody] ChartRenderViewModel chartRenderViewModel)
{
if (chartRenderViewModel == null) { return BadRequest("ChartRenderViewModel is missing!"); }
ChartRenderViewModel = new ChartRenderViewModel();
//ChartRenderViewModel = JsonSerializer.Deserialize<ChartRenderViewModel>(chartRenderViewModel);
ChartRenderViewModel = chartRenderViewModel;
return View(ChartRenderViewModel);
}
[HttpGet, Route("downloadchartreportpdf/")]
public async Task<ActionResult> DownloadChartReportPdfAsync(string id)
{
try
{
var chart = await _cosmosDbService.GetItemAsync<Chart>(
id, _cosmosDbName_reportConfig,
_cosmosDbContainerName_charts,
_cosmosDbPartitionKey_charts, id);
if (chart == null) { return NotFound(); }
var chartRenderViewModel = new ChartRenderViewModel();
chartRenderViewModel!.Chart = chart;
switch (chart.DataSourceType)
{
case "logEvents":
// code block
chartRenderViewModel.ChartData = await _appInsightsQueryRepo.GetChartsFromAppInsightsWithQuery(chart);
break;
}
// Fetch the chart report header html from the repo.
chartRenderViewModel.ChartReportHeaderHtml = await _chartExportRepo.GetChartReportHeaderHtml(chart);
// Get the localhost url from the client request.
var scheme = this.Request.Scheme;
var host = this.Request.Host;
var pathBase = this.Request.PathBase;
switch (chart.ChartType)
{
case "basicAreaChart":
// code block
ChartRenderUrl = "http://" + host + "/chartexport/" + chart.ChartType + "?id=" + id;
break;
default:
// code block
return BadRequest("Chart Type not recognized!");
}
switch (chart.PdfExportOptions!.PaperSize)
{
case "A3":
// code block
switch (chart.PdfExportOptions!.PaperOrientation)
{
case "landscape":
HtmlToPdf.Options.PageSize = new SizeF(EO.Pdf.PdfPageSizes.A3.Height, EO.Pdf.PdfPageSizes.A3.Width);
// Left / Top / Width / Height
// A3 Paper Size = 11.7 x 16.5 inches OR 297 x 420 mm (width x height)
EO.Pdf.HtmlToPdf.Options.OutputArea = new RectangleF(0.5f, 0.5f, 15.5f, 10.7f);
break;
case "portrait":
HtmlToPdf.Options.PageSize = new SizeF(EO.Pdf.PdfPageSizes.A3.Width, EO.Pdf.PdfPageSizes.A3.Height);
// Left / Top / Width / Height
// A3 Paper Size = 11.7 x 16.5 inches OR 297 x 420 mm (width x height)
EO.Pdf.HtmlToPdf.Options.OutputArea = new RectangleF(0.5f, 0.5f, 10.7f, 15.5f);
break;
}
break;
case "A4":
// code block
switch (chart.PdfExportOptions!.PaperOrientation)
{
case "landscape":
HtmlToPdf.Options.PageSize = new SizeF(EO.Pdf.PdfPageSizes.A4.Height, EO.Pdf.PdfPageSizes.A4.Width);
// Left / Top / Width / Height
// A4 Paper Size = 8-1/4 x 11-3/4 inches OR 210 x 297 mm (width x height)
EO.Pdf.HtmlToPdf.Options.OutputArea = new RectangleF(0.5f, 0.5f, 10.75f, 7.25f);
break;
case "portrait":
HtmlToPdf.Options.PageSize = new SizeF(EO.Pdf.PdfPageSizes.A4.Width, EO.Pdf.PdfPageSizes.A4.Height);
// Left / Top / Width / Height
// A4 Paper Size = 8-1/4 x 11-3/4 inches OR 210 x 297 mm (width x height)
EO.Pdf.HtmlToPdf.Options.OutputArea = new RectangleF(0.5f, 0.5f, 7.25f, 10.75f);
break;
}
break;
}
// create memory stream to save PDF
using var pdfStream = new MemoryStream();
EO.Pdf.HtmlToPdf.Options.AddPostData("chartRenderViewModel", JsonSerializer.Serialize(chartRenderViewModel));
//EO.Pdf.HtmlToPdf.Options.AddPostData("chartRenderViewModel", chartRenderViewModel.ToString());
EO.Pdf.HtmlToPdf.Options.AdditionalHeaders = new string[]
{
"Media-Type: application/json"
};
EO.Pdf.HtmlToPdf.ConvertUrl(ChartRenderUrl, pdfStream);
// Reset stream position
pdfStream.Seek(0, SeekOrigin.Begin);
return File(pdfStream.ToArray(), "application/pdf", chart.Name);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
|
|
Rank: Administration Groups: Administration
Joined: 5/27/2007 Posts: 24,217
|
Hi, AddPostData won't work in your case. You will need to use Request object this way:
Code: C#
string json = JsonSerializer.Serialize(chartRenderViewModel);
byte[] data = Encoding.UTF8.GetBytes(json);
Request request = new Request(url);
request.PostData.Add(new PostDataItem(data));
request.ContentType = "application/json";
request.Method = "POST";
webView.LoadRequest(request);
The key difference with the above code is your JSON string is the entire request body. This is what the FromBody attribute wants for your BasicAreaChartAsync method. Where as if you use AddPostData, then the request body will be:
Code:
chartRenderViewModel=your_json_string
This is not the format your BasicAreaChartAsync is expecting because of the FromBody attribute. Alternatively, you could change your BasicAreaChartAsync to take multiple simple arguments such as:
Code: C#
public IActionResult BasicAreaChartAsync(string type, string title, int status, string traceId)
{
.....
}
Then you can call AddPostData for each argument. This is the "classic" way of parameters binding and it should always work. Please let us know if this resolves the issue for you. Thanks!
|
|