I am trying to create a pdf file in MVC using iTextSharp. I do have a following simple used case. File is getting created but when I open the PDF I am getting error file is corrupted unable to open the file. Any idea/help ?
My Controller code is a follows
public FileStreamResult GetPdfMemory()
{
iTextSharp.text.Document doc = new iTextSharp.text.Document();
MemoryStream mem = new MemoryStream();
PdfWriter pdfWriter = PdfWriter.GetInstance(doc, mem);
//pdfWriter.CloseStream = false;
doc.Open();
doc.Add(new Paragraph("Charts"));
mem.Position = 0;
FileStreamResult fileStreamResult = new FileStreamResult(mem, System.Net.Mime.MediaTypeNames.Application.Pdf)
{
FileDownloadName = "chart_" + ".PDF"
};
return fileStreamResult;
}
View :
#Html.ActionLink("Pdf Memory", "GetPdfMemory", "Home", null, new { id = "download"})
FYI : When I try to use FileStream instead of MemoryStream all works fine. But I need to create PDF using memorystream.
You manipulate (mem.Position = 0) and use (new FileStreamResult(mem, ...)) the MemoryStream before signalling iTextSharp that it can finalize the document. Thus, depending on whether implicit destruction of the Document and the PdfWriter or the use of the data in the memory stream comes first, you either have a PDF missing its closing parts or the closing parts (being written after you reposition the memory stream) overwriting the start of the data.
To signal to iTextSharp that it can finalize the document, please call doc.Close() before manipulating the memory stream or alternatively use the Document in a using block, e.g.:
using (MemoryStream ms = new MemoryStream()) {
// step 1
using (Document document = new Document()) {
// step 2
PdfWriter.GetInstance(document, ms);
// step 3
document.Open();
// step 4
document.Add(new Paragraph("HelloWorldMemory"));
}
HttpContext.Current.Response.BinaryWrite(ms.ToArray());
}
(shamelessly copied from the sample HelloWorldMemory.cs from the Webified iTextSharp Examples) for iText in Action — 2nd Edition)
Using using implicitly causes the Document to be closed.
Related
In our ASP.NET MVC web application we send emails as part of scheduled tasks handled by Hangfire for which I am using Postal as described here
The method works fine and we are able to send HTML/text emails. Now we need to generate and attach PDF files as well. The attached PDF needs to be generated dynamically by use of a Razor template. First I tried to use Rotativa in order to generate the PDF. However I encountered the problem that method BuildPdf needs a ControllerContext which is not available in the background HangFire process. I tried to fake the ControllerContext as
using (var memWriter = new StringWriter(sb))
{
var fakeResponse = new HttpResponse(memWriter);
var fakeRequest = new HttpRequest(null, "http://wwww.oururl.com", null);
var fakeHttpContext = new HttpContext(fakeRequest, fakeResponse);
var emailController = new BackgroundEmailController();
var fakeControllerContext = new ControllerContext(new HttpContextWrapper(fakeHttpContext), new RouteData(), emailController);
var attachment = emailController.BillAttachment(email);
var pdf = attachment.BuildPdf(fakeControllerContext);
if (pdf != null && pdf.Count() > 0)
{
using (MemoryStream ms = new MemoryStream(pdf))
{
var contentType = new System.Net.Mime.ContentType(System.Net.Mime.MediaTypeNames.Application.Pdf);
email.Attach(new System.Net.Mail.Attachment(ms, contentType));
}
}
}
However this raised a NullReference error in Rotativa.
Then I tried first to compile the template view with RazorEngine to HTML(and then convert the HTML to pdf by some mean) as
var engineService = RazorEngineService.Create();
engineService.AddTemplate(cache_name, File.ReadAllText(billAttachmentTemplatePath));
engineService.Compile(cache_name, modelType: typeof(BillEmail));
var html = engineService.Run(cache_name, null, email);
using (var ms = CommonHelper.GenerateStreamFromString(html))
{
var contentType = new System.Net.Mime.ContentType(System.Net.Mime.MediaTypeNames.Text.Html);
email.Attach(new System.Net.Mail.Attachment(ms, contentType));
}
And it throws another NullReference in the RazorEngine dynamic DLL:
System.NullReferenceException: Object reference not set to an instance of an object.
at CompiledRazorTemplates.Dynamic.RazorEngine_bb2b366aaef64f2bbc2997353f88cc9e.Execute()
at RazorEngine.Templating.TemplateBase.RazorEngine.Templating.ITemplate.Run(ExecuteContext context, TextWriter reader)
I was wondering if anybody have suggestions for generating PDF files from a template in a Hangfire process?
If you are open to commercial solutions, you can try Telerik reporting and export it as pdf programmatically. You define your report and then invoke it to generate PDF on the server side, finally email the byte[] as email attachment. You can now kickoff this process using Hangfire job.
Here is a pseudo code assuming you have defined the structure of your report, Please look here for more details on how to create your report programatically.
public void GenerateAndEmailReport()
{
var reportSource = new InstanceReportSource();
Telerik.Reporting.Report report = new MyReport();
//populate data into report
reportSource.ReportDocument = report;
var reportProcessor = new ReportProcessor();
reportSource.ReportDocument = report;
var info = new Hashtable();
var result= reportProcessor.RenderReport("PDF", reportSource, info);
byte[]reportBytes = result.DocumentBytes;
SendEmail(reportBytes, "myreport.pdf"); // a method that takes the bytes and attach it to email.
}
Additional references from telerik.
send report as email
Generating PDF in console application
Saving a report programmatically
I want to read a file in my e: drive from asp.net mvc application.
When I try to access it from FileStream Class , file not found exception is thrown. Here is the code.
public byte[] GetEncFile(string path)
{
FileInfo fInfo = new FileInfo(path);
FileStream encFileStream = new FileStream(path, FileMode.Open, FileAccess.Read);
BinaryReader reader = new BinaryReader(encFileStream);
byte[] encFileBytes = reader.ReadBytes((int)fInfo.Length);
return encFileBytes;
}
If Drive exists, then you should use
HttpContext.Current.Server.MapPath
Because you might be using virtual path in mathod paramter.
try :
FileInfo fInfo = new FileInfo(Server.MapPath(path));
will convert virtual path to physical path.
Hope will help.
I am using EPPlus to create an Excel file from a CSV which works without issue. Unfortunately, the below code causes Internet Explorer 9, 10, and 11 to drop the .xlsx file extension, while Chrome and Firefox do not. If I remove the spaces from the file name the file extension works as expected in IE.
public FileStreamResult DetailsExcel(string id)
{
string custName;
var csv = this.GetCsvForCustomer(id, out custName);
var fileName = String.Format("Report for {0} ({1:d-M-yyyy HH mm})",
custName, DateTime.Now);
MemoryStream stream;
using (var excelPackage = new ExcelPackage())
{
var ws = excelPackage.Workbook.Worksheets.Add(fileName);
ws.Cells["A1"].LoadFromText(csv, this._excelTextFormat);
stream = new MemoryStream(excelPackage.GetAsByteArray());
}
return File(stream,
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
fileName + ".xlsx");
}
The two programmatic mechanisms that I have found that somewhat work are to wrap the file name in quotes or to UrlEncode the string. Each have issues:
String.Format("\"{0}\".xlsx", fileName)
// above renders: __Report for Customer (20-2-2014 11 04)__.xlsx
HttpUtility.UrlEncode(fileName + ".xlsx")
// above renders: Report+for+Customer+(20-2-2014+11+04).xlsx
Neither of the above are optimal. How do I include spaces in a file name without losing the file extension when a user is browsing with IE?
I haven't been able to reproduce the behavior you describe in a similar application that I have. The only difference is that I'm not loading the byte array into a MemoryStream first, but just passing the byte array to File.
So try removing the MemoryStream from the equation. So the code would end up something like:
public ActionResult DetailsExcel(string id)
{
byte[] stream; // changed to byte array
using (var excelPackage = new ExcelPackage())
{
var ws = excelPackage.Workbook.Worksheets.Add(fileName);
ws.Cells["A1"].LoadFromText(csv, this._excelTextFormat);
stream = excelPackage.GetAsByteArray(); // removed MemoryStream
}
return File(stream,
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
fileName + ".xlsx");
}
My project is very similar to NerdDinner and I'm generating a pdf-document using PdfSharp.
In my view I'm using this:
<%: Html.ActionLink("Pdf", "GeneratePdf1", "Persons")%>
Calling this ActionResult:
public ActionResult GeneratePdf1()
{
// Create a new PDF document
PdfDocument document = new PdfDocument();
document.Info.Title = "Created with PDFsharp";
// Create an empty page
PdfPage page = document.AddPage();
// Get an XGraphics object for drawing
XGraphics gfx = XGraphics.FromPdfPage(page);
// Create a font
XFont font = new XFont("Verdana", 20, XFontStyle.BoldItalic);
// Draw the text
gfx.DrawString("Hello, World!", font, XBrushes.Black,
new XRect(0, 0, page.Width, page.Height),
XStringFormats.Center);
// Save the document...
const string filename = "HelloWorld.pdf";
document.Save(filename);
Process.Start(filename);
return View();
}
A few questions/problems on this:
I don't want the link to post. When you click the link it should just open the file, but I don't know what to return to prevent it from posting.
I'd also like the "save/open" dialog to appear. Right now the pdf file is displayed directly.
You want to return a FileResult not an ActionResult. Also, I would save it to a MemoryStream and return the byte array, not use a file. Full solution below.
public FileResult GeneratePdf1()
{
// Create a new PDF document
PdfDocument document = new PdfDocument();
document.Info.Title = "Created with PDFsharp";
// Create an empty page
PdfPage page = document.AddPage();
// Get an XGraphics object for drawing
XGraphics gfx = XGraphics.FromPdfPage(page);
// Create a font
XFont font = new XFont("Verdana", 20, XFontStyle.BoldItalic);
// Draw the text
gfx.DrawString("Hello, World!", font, XBrushes.Black,
new XRect(0, 0, page.Width, page.Height),
XStringFormats.Center);
MemoryStream stream = new MemoryStream();
document.Save(stream, false);
return File(stream.ToArray(), "application/pdf");
}
you should replace these lines:
Process.Start(filename);
return View();
with
return File(filename, "application/pdf");
You may also have a third param with the downloaded filename, if it should be different to the action name.
return File(filename, "application/pdf", "myGeneratedPdf.pdf");
Also be sure the filename is unique per request or use a MemoryStream like it is suggested by Chris Kooken.
Btw: Process.Start(filename) will run the file on the server machine. that may work on your development machine, but on a live server the pdf will open on the server!!!
I have an word file that contain my specified pattern text {pattern} and I want to replace those pattern with new my string which was read from database. So I used open xml read stream from my docx template file the replace my pattern string then returned to stream which support to download file without create a temporary file. But when I opened it generated me error on docx file. Below is my example code
public ActionResult SearchAndReplace(string FilePath)
{
MemoryStream mem = new MemoryStream(System.IO.File.ReadAllBytes(FilePath));
using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(mem, true))
{
string docText = null;
using (StreamReader sr = new StreamReader(wordDoc.MainDocumentPart.GetStream()))
{
docText = sr.ReadToEnd();
}
Regex regexText = new Regex("Hello world!");
docText = regexText.Replace(docText, "Hi Everyone!");
//Instead using this code below to write text back the original file. I write new string back to memory stream and return to a stream download file
//using (StreamWriter sw = new //StreamWriter(wordDoc.MainDocumentPart.GetStream(FileMode.Create)))
//{
// sw.Write(docText);
//}
using (StreamWriter sw = new StreamWriter(mem))
{
sw.Write(docText);
}
}
mem.Seek(0, SeekOrigin.Begin);
return File(mem, "application/octet-stream","download.docx"); //Return to download file
}
Please suggest me any solutions instead read a text from a word file and replace those expected pattern text then write data back to the original file. Are there any solutions replace text with WordprocessingDocument libary? How can I return to memory stream with validation docx file format?
The approach you are taking is not correct. If, by chance, the pattern you are searching for matches some Open XML markup, you will corrupt the document. If the text you are searching for is split over multiple runs, your search/replace code will not find the text and will not operate correctly. If you want to search and replace text in a WordprocessingML document, there is a fairly easy algorithm that you can use:
Break all runs into runs of a single
character. This includes runs that
have special characters such as a
line break, carriage return, or hard
tab.
It is then pretty easy to find a
set of runs that match the characters
in your search string.
Once you have identified a set of runs that match,
then you can replace that set of runs
with a newly created run (which has
the run properties of the run
containing the first character that
matched the search string).
After replacing the single-character runs
with a newly created run, you can
then consolidate adjacent runs with
identical formatting.
I've written a blog post and recorded a screen-cast that walks through this algorithm.
Blog post: http://openxmldeveloper.org/archive/2011/05/12/148357.aspx
Screen cast: http://www.youtube.com/watch?v=w128hJUu3GM
-Eric
string sourcepath = HttpContext.Server.MapPath("~/File/Form/s.docx");
string targetPath = HttpContext.Server.MapPath("~/File/ExportTempFile/" + DateTime.Now.ToOADate() + ".docx");
System.IO.File.Copy(sourcepath, targetPath, true);
using (WordprocessingDocument wordDocument = WordprocessingDocument.Open(targetPath, true))
{
string docText = null;
using (StreamReader sr = new StreamReader(wordDocument.MainDocumentPart.GetStream()))
{
docText = sr.ReadToEnd();
}
Regex regexText = new Regex("Hello world!");
docText = regexText.Replace(docText, "Hi Everyone!");
byte[] byteArray = Encoding.UTF8.GetBytes(docText);
MemoryStream stream = new MemoryStream(byteArray);
wordDocument.MainDocumentPart.FeedData(stream);
}
MemoryStream mem = new MemoryStream(System.IO.File.ReadAllBytes(targetPath));
return File(mem, "application/octet-stream", "download.docx");
Writing directly to the word document stream will indeed corrupt it.
You should instead write to the MainDocumentPart stream, but you should first truncate it.
It looks like MainDocumentPart.FeedData(Stream sourceStream) method will do just that.
I haven't tested it but this should work.
public ActionResult SearchAndReplace(string FilePath)
{
MemoryStream mem = new MemoryStream(System.IO.File.ReadAllBytes(FilePath));
using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(mem, true))
{
string docText = null;
using (StreamReader sr = new StreamReader(wordDoc.MainDocumentPart.GetStream()))
{
docText = sr.ReadToEnd();
}
Regex regexText = new Regex("Hello world!");
docText = regexText.Replace(docText, "Hi Everyone!");
using (MemoryStream ms = new MemoryStream())
{
using (StreamWriter sw = new StreamWriter(ms))
{
sw.Write(docText);
}
ms.Seek(0, SeekOrigin.Begin);
wordDoc.MainDocumentPart.FeedData(ms);
}
}
mem.Seek(0, SeekOrigin.Begin);
return File(mem, "application/octet-stream","download.docx"); //Return to download file
}