I have these two methods in my controller.I want to open the FilestreamResult pdf() that returns a file stream result.However, i am getting OutputStream is not available when a custom TextWriter is used error.I am using itextsharp for pdf.
Here is my code :
public FileStreamResult pdf()
{
MemoryStream workStream = new MemoryStream();
Document document = new Document();
PdfWriter.GetInstance(document, workStream).CloseStream = false;
List<Plant> plants = new List<Plant>();
foreach (var item in context.Plants)
{
plants.Add(item);
}
byte[] byteInfo = GeneratePdf(plants);
workStream.Write(byteInfo, 0, byteInfo.Length);
workStream.Position = 0;
return new FileStreamResult(workStream, "application/pdf");
}
and the Generate pdf method is
private static byte[] GeneratePdf(List<Plant> plants)
{
using (MemoryStream memoryStream = new MemoryStream())
{
using (var doc = new Document())
{
PdfWriter.GetInstance(doc, memoryStream);
doc.Open();
doc.SetMargins(120, 120, 270, 270);
BaseFont font = BaseFont.CreateFont(BaseFont.TIMES_ROMAN, BaseFont.CP1252, false);
Font normalFont = new Font(font, 12, Font.NORMAL, BaseColor.BLACK);
Paragraph pgTitle = new Paragraph();
pgTitle.Font = new Font(font, 20, Font.NORMAL, BaseColor.BLACK);
pgTitle.Add("American University of Beirut");
doc.Add(pgTitle);
Paragraph pgPlantTitle = new Paragraph();
pgPlantTitle.Font = new Font(font, 18, Font.NORMAL, BaseColor.BLACK);
pgPlantTitle.Add("Plant Description");
doc.Add(pgPlantTitle);
foreach (Plant p in plants)
{
Paragraph plantDisc = new Paragraph();
plantDisc.Font = new Font(font, 14, Font.NORMAL, BaseColor.BLACK);
plantDisc.Add(p.ScientificName);
plantDisc.Add(p.TypeOfPlants.ToString());
plantDisc.Add(p.PlantHeightRanges.ToString());
plantDisc.Add(p.PlantSpreadRanges.ToString());
plantDisc.Add(p.PlantShapes.ToString());
plantDisc.Add(p.NativeOrigin);
plantDisc.Add(p.Colors.ToString());
plantDisc.Add(p.Colors1.ToString());
plantDisc.Add(p.LightRequirements.ToString());
plantDisc.Add(p.WaterRequirements.ToString());
doc.Add(plantDisc);
doc.Add(new Paragraph(" "));
}
doc.Close();
memoryStream.Close();
return memoryStream.ToArray();
}
}
}
Any help?
You're using the Document and PdfWriter classes incorrectly in your first method. I'm going to throw some comments into that method to better explain what's going on.
public FileStreamResult pdf()
{
//Create a generic Stream for someone to write their bytes to
MemoryStream workStream = new MemoryStream();
//Create an iText Document helper object which is a friendly way to create new PDFs using things like tables and paragraphs.
//No where in the code below will this helper object be used so that's the first problem.
Document document = new Document();
//Bind our document helper and stream to a PdfWriter.
//This writer will _exclusively own_ the Stream from now on.
//If _anyone_ else writes to the stream (as you are doing below) it will break the PDF or possibly just throw an exception
PdfWriter.GetInstance(document, workStream).CloseStream = false;
//Business logic here unrelated to the problem
List<Plant> plants = new List<Plant>();
foreach (var item in context.Plants)
{
plants.Add(item);
}
//Create a byte array that represents a PDF. The GeneratePdf appears to be correct.
byte[] byteInfo = GeneratePdf(plants);
//Even though we declared above that we want our PdfWriter to have exclusive access to the Stream,
//ignore that and write our byte array to it.
workStream.Write(byteInfo, 0, byteInfo.Length);
//Rewind the stream
workStream.Position = 0;
return new FileStreamResult(workStream, "application/pdf");
}
Hopefully those comments make sense. Your GeneratePdf() method is what makes a PDF. Once you have a valid PDF, unless you want to modify it or inspect it you no longer have any need for iTextSharp. So your first method should be changed to something like the below. (I don't have VS available right now but this should probably compile except for a possible typo or two.)
//Business logic
List<Plant> plants = new List<Plant>();
foreach (var item in context.Plants)
{
plants.Add(item);
}
//Create our PDF
byte[] byteInfo = GeneratePdf(plants);
//Wrap the bytes in a Stream and return
using( var workStream = new MemoryStream( byteInfo ) )
{
return new FileStreamResult(workStream, "application/pdf");
}
The way you've described the Exception, am making an educated guess that your problem is in a view linking to your controller/action. For example if you have a View and are creating a hyperlink like the commented section:
#* remove comment to see Exception
<h2>Exception: "OutputStream is not available when a custom TextWriter is used."</h2>
<p>
This throws an Exception
</p>
*#
<h2>Correct!</h2>
<p>
<a href="#Url.Action("IndexPdf")" target='_blank'>This works!</a>
</p>
The exact Exception you describe is thrown:
System.Web.HttpException: OutputStream is not available when a custom
TextWriter is used.
So use Url.Action().
Aside from that, a few notes about the GeneratePdf() method in your code:
Remove the Close() calls on MemoryStream and Document, since they're both in using statements.
Move the MemoryStream call to ToArray() outside the Document using block. Otherwise the PDF result may be corrupted.
A shortened example based on your code:
private static byte[] GeneratePdf(List<Plant> plants)
{
using (MemoryStream memoryStream = new MemoryStream())
{
using (var doc = new Document())
{
PdfWriter.GetInstance(doc, memoryStream);
doc.Open();
doc.SetMargins(120, 120, 270, 270);
Paragraph pgTitle = new Paragraph("TEST");
doc.Add(pgTitle);
// initialize title, etc, here
// and iterate over plants here
}
// return **AFTER** Document is disposed
return memoryStream.ToArray();
}
}
And a few notes on your pdf() Action:
Maybe a typo or copy/paste error, but there's no reason for Document or PdfWriter.GetInstance()
If you return a less specific ActionResult instead of the FileStreamResult, you do not have to make an in-memory copy of the PDF. I.e. you can eliminate the MemoryStream, and instead call Controller.File(), since the first parameter is a byte array:
Another shortened example based on your code, this time for the controller action:
public ActionResult IndexPdf()
{
var plants = new List<Plant>();
// get your plants here
byte[] byteInfo = GeneratePdf(plants);
return File(byteInfo, "application/pdf");
}
I have the following code:
public byte[] ExportToPdf(DataTable dt)
{
iTextSharp.text.Document document = new iTextSharp.text.Document();
document.Open();
iTextSharp.text.Font font5 = iTextSharp.text.FontFactory.GetFont(FontFactory.HELVETICA, 5);
PdfPTable table = new PdfPTable(dt.Columns.Count);
PdfPRow row = null;
float[] widths = new float[] { 4f, 4f, 4f, 4f, 4f, 4f, 4f, 4f };
table.SetWidths(widths);
table.WidthPercentage = 100;
foreach (DataColumn c in dt.Columns)
{
table.AddCell(new Phrase(c.ColumnName, font5));
}
document.Add(table);
document.Close();
byte[] bytes;
MemoryStream msPDFData = new MemoryStream();
PdfWriter writer = PdfWriter.GetInstance(document, msPDFData);
return msPDFData.ToArray();
}
And in another function i call the function like this:
byte[] bytes = ExportToPdf(table);
return File(bytes, "application/pdf", "RaportDocumenteEmise.pdf");
When i try to open the pdf it says that is damaged.
Somehow the byte array is empty.
Can say me what am i doing wrong?
This is wrong:
iTextSharp.text.Document document = new iTextSharp.text.Document();
document.Open();
A PDF is created using 5 simple steps:
Create a Document object
Create a PdfWriter instance
Open the document
Add content
Close the document
You don't have step 2. In your comment, you say that you've solved the problem by creating that instance after opening the document, but that's wrong! You need to create the PdfWriter instance before opening the document.
Opening the document writes the PDF header to the OutputStream. That can't happen without a valid PdfWriter instance.
I was trying to do the same thing as you.
After many tries, I discovered that if I put PdfWriter.GetInstance
inside using (var ms = new MemoryStream()) { } everything works alright!
My full code is:
public FileContentResult GetPDF() {
string htmlContent = "<p>First line</p><p>Second line</p>";
StringReader sr = new StringReader(htmlContent);
Document pdfDoc = new Document(PageSize.A4, 10f, 10f, 100f, 0f);
HTMLWorker hw= new HTMLWorker(pdfDoc);
FileContentResult result;
using (var ms = new MemoryStream()) {
PdfWriter.GetInstance(pdfDoc, ms);
pdfDoc.Open();
hw.Parse(sr);
pdfDoc.Close();
result = this.File(ms.ToArray(), "application/pdf", "teste.pdf");
}
return result;
}
Ps.: This is a method inside my Controller.
I created a pdf form in LiveCycle, saved and reader enabled. I have 2 buttons on the form. The first button hits a webservice to populate a dropdown list n the form, and the other button is a submit, which submits the XML data to an MVC Controller method and puts the xml data in a sql database. There is another MVC Controller method the uses itextsharp to open the LiveCycle pdf and fill it with the xml data from SQL. My problem is that the form, after it uses the itextSharp stuff to do its thing, need to be able to resubmit any changes to the form using the submit button. 2 things are happening, and I am not Flattening the form. 1) The button that hits the webservice to populate the dropdownlist does not show unless I scroll out of view and back in. But even then, the dropdownlist shows what was chosen, but cannot be repopulated. Like the button doesn't work or something. 2) The submit button will not work either. I can click, but it does nothing. Any help is appreciated. My code that uses itextsharp is below.
public void PDFBuilder(int FormID)
{
int id = 0;
id = FormID;// Convert.ToInt32(Request.QueryString["ID"]);
var oRow = db.SubmissionsGenerals.Find(id);
string fv = db.FormVersions.Where(w => w.GUID == oRow.FormGUID).Select(s => s.FormPathFillable).First();
string xML = string.Empty;
xML = oRow.XMLData.ToString();
XmlDocument oXmlData = new XmlDocument();
oXmlData.LoadXml(xML);
string m_FormsPath = "http://myformsurl" + fv.TrimStart('.');
fv = fv.TrimStart('.');
if (fv != null)
{
MemoryStream ms = GeneratePDF(m_FormsPath, oXmlData);
byte[] bytes = ms.ToArray();
Response.ContentType = "application/pdf";
Response.BinaryWrite(bytes);
Response.End();
}
}
public MemoryStream GeneratePDF(string m_FormName, XmlDocument oData)
{
PdfReader pdfTemplate;
PdfStamper stamper;
PdfReader tempPDF;
Document doc;
MemoryStream msTemp;
PdfWriter pCopy;
MemoryStream msOutput = new MemoryStream();
pdfTemplate = new PdfReader(m_FormName);
doc = new Document();
pCopy = new PdfCopy(doc, msOutput);
pCopy.AddViewerPreference(PdfName.PICKTRAYBYPDFSIZE, new PdfBoolean(true));
pCopy.AddViewerPreference(PdfName.PRINTSCALING, PdfName.NONE);
doc.Open();
for(int i=1; i<pdfTemplate.NumberOfPages + 1; i++)
{
msTemp = new MemoryStream();
pdfTemplate = new PdfReader(m_FormName);
stamper = new PdfStamper(pdfTemplate, msTemp);
foreach(XmlElement oElem in oData.SelectNodes("/form1/*"))
{
stamper.AcroFields.SetField(oElem.Name, oElem.InnerText);
}
//stamper.FormFlattening = true;
stamper.Close();
tempPDF = new PdfReader(msTemp.ToArray());
((PdfCopy)pCopy).AddPage(pCopy.GetImportedPage(tempPDF, i));
pCopy.FreeReader(tempPDF);
}
doc.Close();
return msOutput;
}
i have a view where i put the id of the event then i can download all the images for that event.....
here's my code
[HttpPost]
public ActionResult Index(FormCollection All)
{
try
{
var context = new MyEntities();
var Im = (from p in context.Event_Photos
where p.Event_Id == 1332
select p.Event_Photo);
Response.Clear();
var downloadFileName = string.Format("YourDownload-{0}.zip", DateTime.Now.ToString("yyyy-MM-dd-HH_mm_ss"));
Response.ContentType = "application/zip";
Response.AddHeader("content-disposition", "filename=" + downloadFileName);
using (ZipFile zipFile = new ZipFile())
{
zipFile.AddDirectoryByName("Files");
foreach (var userPicture in Im)
{
zipFile.AddFile(Server.MapPath(#"\") + userPicture.Remove(0, 1), "Files");
}
zipFile.Save(Response.OutputStream);
//Response.Close();
}
return View();
}
catch (Exception ex)
{
return View();
}
}
The problem is that each time i get html page to download so instead of downloading "Album.zip" i get "Album.html" any ideas???
In MVC, rather than returning a view, if you want to return a file, you can return this as an ActionResult by doing:
return File(zipFile.GetBytes(), "application/zip", downloadFileName);
// OR
return File(zipFile.GetStream(), "application/zip", downloadFileName);
Don't mess about with manually writing to the output stream if you're using MVC.
I'm not sure if you can get the bytes or the stream from the ZipFile class though. Alternatively, you might want it to write it's output to a MemoryStream and then return that:
var cd = new System.Net.Mime.ContentDisposition {
FileName = downloadFileName,
Inline = false,
};
Response.AppendHeader("Content-Disposition", cd.ToString());
var memStream = new MemoryStream();
zipFile.Save(memStream);
memStream.Position = 0; // Else it will try to read starting at the end
return File(memStream, "application/zip");
And by using this, you can remove all lines in which you are doing anything with the Response. No need to Clear or AddHeader.
I would like to call an action on a controller. Have the controller get the data from the model. The view then runs and generates a PDF. The only example I have found is in an article by Lou http://whereslou.com/2009/04/12/returning-pdfs-from-an-aspnet-mvc-action. His code is very elegant. The view is using ITextSharp to generate the PDF. The only downside is his example uses the Spark View Engine. Is there a way to do a similar thing with the standard Microsoft view engine?
I use iTextSharp to generate dynamic PDF's in MVC. All you need to do is put your PDF into a Stream object and then your ActionResult return a FileStreamResult. I also set the content-disposition so the user can download it.
public FileStreamResult PDFGenerator()
{
Stream fileStream = GeneratePDF();
HttpContext.Response.AddHeader("content-disposition",
"attachment; filename=form.pdf");
return new FileStreamResult(fileStream, "application/pdf");
}
I also have code that enables me to take a template PDF, write text and images to it etc (if you wanted to do that).
Note: you must set the Stream position to 0.
private Stream GeneratePDF()
{
//create your pdf and put it into the stream... pdf variable below
//comes from a class I use to write content to PDF files
MemoryStream ms = new MemoryStream();
byte[] byteInfo = pdf.Output();
ms.Write(byteInfo, 0, byteInfo.Length);
ms.Position = 0;
return ms;
}
our final answer to this problem was to use Rotativa.
It wraps up the WKhtmltopdf.exe like some of the other solutions, but it's by far the easiest to use that I have found
I went and up voted all the other answers that also solve the problem well, but this is what we used to solve the problem posed in the question above. It is different from the other answers.
Here is a Rotativa Tutorial.
after you install it, this is all your need
public ActionResult PrintInvoice(int invoiceId)
{
return new ActionAsPdf(
"Invoice",
new { invoiceId= invoiceId })
{ FileName = "Invoice.pdf" };
}
Very Very simple.
Creating layout in html and printing afterwards into pdf is the fastest way.
Html into pdf conversion is provided by phantomjs, wkhtmltopdf or jsreport
jsreport provides direct integration with asp.net mvc views,
where you can just mark controller action with attribute and it will print pdf instead of html for you.
More on this blog post
Disclaimer: I am the author of jsreport
This is an old question but one that's still relevant and I thought I'd just share what I've implemented which works well.
Install NuGet package TuesPechkin - a fork in the Pechkin library based on WkHtmlToPdf that uses a Webkit engine to convert HTML pages to PDF.
Write a little helper to read a view and convert it to an HTML string (mvcContext is this.HttpContext). The replace is optional of course!:
public static string RenderViewToString(HttpContextBase mvcContext, string area, string controllerName, string viewName, object model)
{
var context = System.Web.HttpContext.Current;
var contextBase = mvcContext;
var routeData = new RouteData();
if (area == null) area = "";
routeData.DataTokens.Add("area", area);
routeData.Values.Add("controller", controllerName);
var controllerContext = new ControllerContext(contextBase,
routeData,
new EmptyController());
var razorViewEngine = new RazorViewEngine();
var razorViewResult = razorViewEngine.FindView(controllerContext,
viewName,
"",
false);
var writer = new StringWriter();
var viewContext = new ViewContext(controllerContext,
razorViewResult.View,
new ViewDataDictionary(model),
new TempDataDictionary(),
writer);
razorViewResult.View.Render(viewContext, writer);
string hostAddress = context.Request.Url.Scheme + "://" + context.Request.Url.Authority;
return writer.ToString()
.Replace("src=\"/", "src=\"" + hostAddress + "/")
.Replace("<link href=\"/", "<link href=\"" + hostAddress + "/");
}
class EmptyController : ControllerBase
{
protected override void ExecuteCore() { }
}
The hard work of the above were from here: http://wouterdekort.blogspot.co.uk/2012/10/rendering-aspnet-mvc-view-to-string-in.html?showComment=1414603363455#c7863520150405064571
Create an MVC Action to generate the document
public ActionResult DownloadPDF(long CentreID)
{
var model = GetModel()
IPechkin converter = Factory.Create();
byte[] result = converter.Convert(Helpers.PDF.RenderViewToString(this.HttpContext, "area", "controller", "action", model);
MemoryStream outputStream = new MemoryStream();
outputStream.Write(result, 0, result.Length);
outputStream.Position = 0;
return File(outputStream, "application/pdf", "filename.pdf");
}
I also came across this http://www.codeproject.com/Articles/260470/PDF-reporting-using-ASP-NET-MVC3. It's easy and swift, and fits well with MVC.
However, the only downside so far is that it's not quite flexible you want to have a decent layout, for example, you don't have much control with table, and cell borders through html. It sort of supports force new page, but you will have to apply a patch to iTextsharp.
I just used wkhtmltopdf, to create the layout in html and afterwards, i convert it to pdf.
Easy, customizable, awesome as hell :)
Very Late Reply, but I found that the following URL helped me get my results quickly :
(Ensure that you reference to the iTextSharp DLL by making use of the Nuget Packages)
https://www.aspsnippets.com/Articles/Export-Grid-Html-Table-data-from-database-to-PDF-file-using-iTextSharp-in-ASPNet-MVC.aspx
EDIT
This is the code I used to make the table look a little different(This is landscape as well:
public string GetCssForPdf()
{
string css = "";
css = "th, td" +
"{" +
"font-family:Arial; font-size:10px" +
"}";
return css;
}
[HttpPost]
[ValidateInput(false)]
public FileResult Export(string GridHtml)
{
string webgridstyle = GetCssForPdf();
string exportData = String.Format("<html><body>{0}{1}</body></html>", "<style>" + webgridstyle + "</style>", GridHtml);
var bytes = System.Text.Encoding.UTF8.GetBytes(exportData);
using (var input = new MemoryStream(bytes))
{
var output = new MemoryStream();
var document = new iTextSharp.text.Document(PageSize.A4, 50, 50, 50, 50);
var writer = PdfWriter.GetInstance(document, output);
document.SetPageSize(iTextSharp.text.PageSize.A4.Rotate());
Font headerFont = FontFactory.GetFont("Verdana", 10);
Font rowfont = FontFactory.GetFont("Verdana", 10);
writer.CloseStream = false;
document.Open();
var xmlWorker = iTextSharp.tool.xml.XMLWorkerHelper.GetInstance();
xmlWorker.ParseXHtml(writer, document, input, System.Text.Encoding.UTF8);
document.Close();
output.Position = 0;
return File(output, "application/pdf", "Pipeline_Report.pdf");
//return new FileStreamResult(output, "application/pdf");
}
}
Hope this helps someone else as well.
A small example using rotativa package in asp.net mvc
We will create a function to populate the data. We will insert data for 7 days (Feb 1 2018 - Feb 7 2018) showing the first punch and the last punch for particular day with remarks.
public ReportViewModel PopulateData()
{
var attendances = new List<Attendance>
{
new Attendance{ClassName = "present",Day = new DateTime(2018, 02, 01).ToString("ddd"),Date = new DateTime(2018, 02, 01).ToString("d"),FirstPunch = "09:01:00",LastPunch = "06:00:01",Remarks = ""},
new Attendance{ClassName = "absent",Day = new DateTime(2018, 02, 02).ToString("ddd"),Date = new DateTime(2018, 02, 02).ToString("d"),FirstPunch = "",LastPunch = "",Remarks = "Absent"},
new Attendance{ClassName = "holiday",Day = new DateTime(2018, 02, 03).ToString("ddd"),Date = new DateTime(2018, 02, 03).ToString("d"),FirstPunch = "",LastPunch = "",Remarks = "Democracy Day"},
new Attendance{ClassName = "present",Day = new DateTime(2018, 02, 04).ToString("ddd"),Date = new DateTime(2018, 02, 04).ToString("d"),FirstPunch = "09:05:00",LastPunch = "06:30:01",Remarks = ""},
new Attendance{ClassName = "present",Day = new DateTime(2018, 02, 05).ToString("ddd"),Date = new DateTime(2018, 02, 05).ToString("d"),FirstPunch = "09:01:00",LastPunch = "06:00:01",Remarks = ""},
new Attendance{ClassName = "leave",Day = new DateTime(2018, 02, 06).ToString("ddd"),Date = new DateTime(2018, 02, 06).ToString("d"),FirstPunch = "",LastPunch = "",Remarks = "Sick Leave"},
new Attendance{ClassName = "present",Day = new DateTime(2018, 02, 07).ToString("ddd"),Date = new DateTime(2018, 02, 07).ToString("d"),FirstPunch = "08:35:00",LastPunch = "06:15:01",Remarks = ""}
};
return new ReportViewModel
{
UserInformation = new UserInformation
{
FullName = "Ritesh Man Chitrakar",
Department = "Information Science"
},
StartDate = new DateTime(2018, 02, 01),
EndDate = new DateTime(2018, 02, 07),
AttendanceData = attendances
};
}
We will then create a function to DownloadPdf. To download the pdf we will need to create 2 function.
1. to download pdf
2. to view pdf
public ActionResult DownloadPdf()
{
var filename = "attendance.pdf";
/*get the current login cookie*/
var cookies = Request.Cookies.AllKeys.ToDictionary(k => k, k => Request.Cookies[k]?.Value);
return new ActionAsPdf("PdfView", new
{
startDate = Convert.ToDateTime(Request["StartDate"]),
endDate = Convert.ToDateTime(Request["EndDate"])
})
{
FileName = filename,
/*pass the retrieved cookie inside the cookie option*/
RotativaOptions = {Cookies = cookies}
};
}
public ActionResult PdfView()
{
var reportAttendanceData = PopulateData();
return View(reportAttendanceData);
}
You can view the detail explanation on this link .
Visit here .
Curtesoy : thelearninguy.com