How to render an ASP.NET MVC View in PDF format - c#

I'm working with ExpertPDF's Html-to-PDF conversion utility for this question (although I'm open to other libraries if there's sufficient documentation).
In short, I have a view that is formatted a specific way and I would like to render it as a PDF document the user can save to disk.
What I have so far is a PrintService (which implements an IPrintService interface) and this implementation has two overloads for PrintToPDF(), one that takes just a URL and another that takes an HTML string, and both of which return a byte[]. I've only worked out the details of the second overload which requires the HTML string.
What I would like to do from my controller is something like:
public FileStreamResult Print(int id)
{
var model = _CustomRepository.Get(id);
string renderedView = SomethingThatRendersMyViewAsAString(model);
Stream byteStream = _PrintService.PrintToPdf(renderedView);
HttpContext.Response.AddHeader("content-disposition",
"attachment; filename=report.pdf");
return new FileStreamResult(byteStream, "application/pdf");
}
which in theory would render a PDF to the page. It's the "SomethingThatRendersMyViewAsAString" that I'm looking for help with. Is there a quick way to get the string representation of a View? Or perhaps I should just stick with the URL overload and pass in a URL to the view... Any other thoughts?
Thanks!

I packaged my solution in a Nuget package: Rotativa http://nuget.org/packages/Rotativa. It's based on wkhtmltopdf.
Usage is really simple.
Having an action you would like to serve as Pdf, instead of Html page. You can define an action that returns an ActionResult of the type ActionAsPdf (RouteAsPdf is also available).
So the code is just:
public ActionResult PrintIndex()
{
return new ActionAsPdf("Index", new { name = "Giorgio" }) { FileName = "Test.pdf" };
}
With name = "Giorgio" being a route parameter.
It works even if the action to print is protected by web forms authentication ([Authorize] attribute)

You might be able to tap into the Response during OnResultExecuting and replace the Filter property with something that stores the resultant HTML in a MemoryStream. Then you could clear the Response during OnResultExecuted and replace it with the results of your PDF conversion. I'm not sure that this would be better than just getting the HTML from the URL, though.
public FileStreamResult Print(int id)
{
var model = _CustomRepository.Get(id);
this.ConvertToPDF = true;
return View( "HtmlView" );
}
public override OnResultExecuting( ResultExecutingContext context )
{
if (this.ConvertToPDF)
{
this.PDFStream = new MemoryStream();
context.HttpContext.Response.Filter = new PDFStreamFilter( this.PDFStream );
}
}
public override OnResultExecuted( ResultExecutedContext context )
{
if (this.ConvertToPDF)
{
context.HttpContext.Response.Clear();
this.PDFStream.Seek( 0, SeekOrigin.Begin );
Stream byteStream = _PrintService.PrintToPDF( this.PDFStream );
StreamReader reader = new StreamReader( byteStream );
context.HttpContext.Response.AddHeader( "content-disposition",
"attachment; filename=report.pdf" );
context.HttpContext.Response.AddHeader( "content-type",
"application/pdf" );
context.HttpContext.Response.Write( reader.ReadToEnd() );
}
}
The PDFStreamFilter would need to override the "Write" method(s) and send the data to the memory stream instead.

This sounds like a similar problem I had where I wanted to use Views as email templates. The best answer I found for getting the string representation of a View was here: Render a view as a string

The best package I've found is the RazorPDF, available as a package at NuGet.org, based on iTextSharp. Works on Azure Web Sites:
https://nuget.org/packages/RazorPDF

Related

How to produce a file to download containing a JSON structure?

I have this method in my controller.
public IActionResult Download()
{
return Json(_context.Users);
}
I noticed that it produces the correct JSON structure but it's being rendered in the browser as common text. I want it to be downloaded to the client's computer. How do I do that?
I'm not sure if is should make my object to stream somehow like this or maybe create a file on my hard drive and serve it like this.
I can't find anything that strikes me as straight-forward and simple like we're used to in C#. So I fear that I'm missing a concept here.
You can just write json object to a stream or array and use one of File method overloads. Add convenient Serialize method
private byte[] Serialize(object value, JsonSerializerSettings jsonSerializerSettings)
{
var result = JsonConvert.SerializeObject(value, jsonSerializerSettings);
return Encoding.UTF8.GetBytes(result);
}
And use it as following
public IActionResult Download()
{
var download = Serialize(_context.Users, new JsonSerializerSettings());
return File(download , "application/json", "file.json");
}
If you set special json serializer settings in Startup using .AddJsonOptions() you would like to use them as ASP.NET framework uses them in Json method. Inject MvcJsonOptions in controller
IOptions<MvcJsonOptions> _options;
public YourController(IOptions<MvcJsonOptions> options)
{
_options = options;
}
And pass settings to method
public IActionResult Download()
{
var download = Serialize(_context.Users, _options.Value.SerializerSettings);
return File(download , "application/json", "file.json");
}
Convert the data into bytes then those bytes into a FileResult. You return the FileResult and the browser will do whatever it does normally when presented with a 'file', usually either prompt the user or download.
Example below:
public ActionResult TESTSAVE()
{
var data = "YourDataHere";
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(data);
var output = new FileContentResult(bytes, "application/octet-stream");
output.FileDownloadName = "download.txt";
return output;
}
In your case you would simply take your JSON data as a string.

Returning a PDF from an ASP.NET Core 2 Controller

I am trying to return a PDF file from my ASP.NET Core 2 controller.
I have this code
(mostly borrowed from this SO question):
var net = new System.Net.WebClient();
//a random pdf file link
var fileLocation = "https://syntera.io/documents/T&C.pdf";/
var data = net.DownloadData(fileLocation);
MemoryStream content = null;
try
{
content = new MemoryStream(data);
return new FileStreamResult(content, "Application/octet-stream");
}
finally
{
content?.Dispose();
}
This code above is part of a service class that my controller calls. This is the code from my controller.
public async Task<IActionResult> DownloadFile(string fileName)
{
var result = await _downloader.DownloadFileAsync(fileName);
return result;
}
But I keep getting ObjectDisposedException: Cannot access a closed Stream.
The try and finally block was an attempt to fix it , from another SO question .
The main question is A) Is this the right way to send a PDF file back to the browser and B) if it isn't, how can I change the code to send the pdf to the browser?
Ideally , I don't want to first save the file on the server and then return it to the controller. I'd rather return it while keeping everything in memory.
The finally will always get called (even after the return) so it will always dispose of the content stream before it can be sent to the client, hence the error.
Ideally , I don't want to first save the file on the server and then return it to the controller. I'd rather return it while keeping everything in memory.
Use a FileContentResult class to take the raw byte array data and return it directly.
FileContentResult: Represents an ActionResult that when executed will write a binary file to the response.
async Task<IActionResult> DownloadFileAsync(string fileName){
using(var net = new System.Net.WebClient()) {
byte[] data = await net.DownloadDataTaskAsync(fileName);
return new FileContentResult(data, "application/pdf") {
FileDownloadName = "file_name_here.pdf"
};
}
}
No need for the additional memory stream
You must specify :
Response.AppendHeader("content-disposition", "inline; filename=file.pdf");
return new FileStreamResult(stream, "application/pdf")
For the file to be opened directly in the browser.

How use the Byte Array of a image?

So, i am getting the byte array of a LongRaw image from Oracle...
I am using a webapi to this. After get the array, how i use it on the Client-side ?
Do Its better i convert to base64string and pass this value converting just at the client side ?
cmd.InitialLONGFetchSize = -1;
var reader = cmd.ExecuteReader();
if (reader.Read())
{
// Fetch the LONG RAW
OracleBinary imgBinary = reader.GetOracleBinary(0);
// Get the bytes from the binary obj
byte[] imgBytes = imgBinary.IsNull ? null : imgBinary.Value;
//var imgString = Uri.EscapeDataString(Convert.ToBase64String(imgBytes));
}
//CRIO A LISTA
lretorno.Load(reader, LoadOption.OverwriteChanges, "BUSCAFOTO");
reader.Close();
connection.Close();
connection.Dispose();
var teste = lretorno.Tables[0].AsEnumerable().Select(row => new FotoEnvolvido
{
FOTO = (byte[])(row["FOTO"]),
//FOTO = Convert.ToString(row["FOTO"]),
});
return teste;
You can write a Web API Controller that returns the binary data of an image. Base64 strings impose a overhead of the amount of bytes that have to be transmitted. Please avoid this.
A sample controller can look like this example:
public class WebApiController : ApiController
{
public async Task<HttpResponseMessage> Get(string id)
{
var bytes = await GetBytesFromDataLayerAsync(id);
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
var stream = new MemoryStream(bytes);
result.Content = new StreamContent(stream);
result.Content.Headers.ContentType =
new MediaTypeHeaderValue("image/jpeg");
return result;
}
private async Task<byte[]> GetBytesFromDataLayerAsync(string id)
{
// put your Oracle logic here
return ...
}
}
Depending on what your doing as rboe said writing the bytes directly to the client will save some data size(approx. 37%) and computing overhead. If your not only displaying jpeg images you should also set the mime-type to the correct value... take a look at this source for a rather complete set of extension to mime-type mappings. If you do not know the mime-type you can try "application/octet-stream" as that is the general mime-type for binary data.
If your displaying your content via web browser you could just use an <img> tag something like <img src="view_image.aspx?id=5"> you can even create the dynamically with javascript/jQuery.
If you really do want the image data embedded in a json request which might be useful if you have a lot of little icons and don't want a ton of requests (with http/2 I don't think this will matter) or another reason, then yes first encode the binary data using...
string base64EncodedData = Convert.ToBase64String(bytes);
If the client is javascript you can decode using the latest browsers native functions
var decodedImageData = window.atob(base64EncodedData);
See:
mozilla.org docs
This answer
This answer
If you are however just sending it to another c# endpoint you can use...
byte[] decodedImageData = Convert.FromBase64String(base64EncodedData);
And like I mentioned in the comment to ensure it's encrypted just make the site only support https:// and if you don't have a SSL cert you can get one for free from http://startssl.com

Open PDF reusing code

How to open pdf in my asp.net mvc website? But reusing code and which I have not. I do not want to do this for each pdf, so I want to reuse code.
My Index View:
<p>#Html.ActionLink("Historia.pdf", "ObtenerPdf", "Home")</p>
My Controller Method:
public FileStreamResult ObtenerPdf()
{
FileStream fs = new FileStream("c:\\Historia.pdf", FileMode.Open, FileAccess.Read);
return File(fs, "application/pdf");
}
Untested, I don't have VS in front of me to check.
In summary, you need to add a parameter to your DownloadPdf action and pass it in with the action link.
Here's some code to list all the PDFs (Q used C:\ so this does as well, see notes), show them to the user and download.
Controller:
public ActionResult Index()
{
var files = System.IO.Directory.GetFiles(#"C:\", "*.pdf")
.Select(x => System.IO.Path.GetFileNameWithoutExtension(x))
.ToList();
return View(files);
}
public FileStreamResult ObtenerPdf(string file)
{
FileStream fs = new FileStream("c:\\" + file + ".pdf", FileMode.Open, FileAccess.Read);
return File(fs, "application/pdf");
}
View:
#model IList<string>
#foreach (var file in Model)
{
<p>#Html.ActionLink(file, "ObtenerPdf", "Home", new { file }, null)</p>
}
Couple of notes:
Directory.GetFiles includes the path - there are other methods to get a list of files without the path included.
ActionLink overload is: text, action, controller, routeValues, attributes - without the null it will pick the wrong overload.
In new { file } if you don't specify a name, it will use the variable name, you could be specific to match the parameter name on the action which would be more robust, ie: new { file = file } but some optimisers will highlight this.
You probably don't want to list all your PDFs on C:\ on the server... you can use Server.MapPath to locate a folder within your application (then it doesn't matter if your site moves)
There's very likely an overload for return File to specify the path, so you don't need to open it first and will dispose it for you correctly etc, I've used the same code as supplied in the question.

How should I return a large text file using ServiceStack?

I have a web service that needs to return a large text file for an AJAX call on the client. For starters, I have a valid path to the text file:
var fileName = <file on server>
I know the file name is valid, because I can open it on the server as a FileStream.
I have tried some of the different approaches recommended in ServiceStack and returning a stream, but I can't seem to get it to work.
What should I return? My best guess was:
var stream = File.Open(fileName, ...);
return HttpResult(stream, "text/plain"){ AllowPartialResponse = false };
But that doesn't work; the response is a simple JSON object. Using FileInfo with the asAttachment option didn't work either; it just returned a bunch of file information.
The goal is for the JavaScript client to be able to receive the content as a string:
api.getFile({...}).then( function (result) {
// result has the file contents
});
What is the correct way to do this?
Update:
I ended up using this stream approach to get it working:
using( var fs = File.OpenRead( fileName ) )
{
fs.CopyTo( Response.OutputStream );
}
I don't fully understand why some of the other approaches didn't work, but they appear to be related to 1) FileInfo and how it behaves on the server, 2) file permissions in my development environment, and 3) "partial content" problems and exceptions. File.Exists() returns false, but not in debug discusses one of the problems that was throwing me off.
I've just committed a Service that returns a text file and the HttpResult API's work as expected:
[Route("/textfile-test")]
public class TextFileTest
{
public bool AsAttachment { get; set; }
}
public class MyServices : Service
{
public object Any(TextFileTest request)
{
return new HttpResult(new FileInfo("~/textfile.txt".MapHostAbsolutePath()),
asAttachment:request.AsAttachment);
}
}
Which you can test out with or without the asAttachment option:
http://test.servicestack.net/textfile-test
http://test.servicestack.net/textfile-test?asAttachment=true
You can also access the text file directly (i.e. without a Service):
http://test.servicestack.net/textfile.txt
In addition, the different ways to return an Image response should also apply to text files as well.

Categories