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");
}
Related
I want to create a HttpResponse that streams a local file.
I want to use a MemoryStream, so that I can delete the file afterwards (well actually before returning the repsonse).
I always end up with an empty response although the stream seems to be valid.
Working with a FileStream in API Controller works, though.
public HttpResponseMessage GetExcelFile(Guid id)
{
// this model is needed to internally create an .xls file that represents this model
var exportModel = this.myService.GetExport(id);
// this approach does not work -> respone always empty although memory stream has content
// var stream = new MemoryStream();
// internally creates a .xls file (using lib) and returns its content as memory stream
// this.myService.ConvertToStream(exportModel, stream));
// this works fine
var stream = File.OpenRead(#"D:\test0815.xls");
var result = this.Request.CreateResponse(HttpStatusCode.OK);
result.Content = new StreamContent(stream);
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
result.Content.Headers.ContentLength = stream.Length;
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = $"{exportModel.Name}-Sheet.xls"
};
return result;
}
this is my method that actually converts to memorystream:
private MemoryStream SaveToStream(MemoryStream stream)
{
using (FileStream source = File.Open(
#"D:\test0815.xls",
FileMode.Open))
{
Console.WriteLine("Source length: {0}", source.Length.ToString());
// Copy source to destination.
source.CopyTo(stream);
}
return stream;
}
I also tried writing to memory stream but this did not work either.
It seems that result.Content = new StreamContent(stream); is just not working with an memory stream.
Any ideas?
finally I found a working solution:
var memoryStream = new MemoryStream((int)fileStream.Length);
fileStream.CopyTo(memoryStream);
fileStream.Close();
memoryStream.Seek(0, SeekOrigin.Begin);
HttpContent content = new StreamContent(memoryStream);
var result = this.Request.CreateResponse(HttpStatusCode.OK);
result.Content =content;
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
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 am currently assembling and displaying a PDF using RazorPDF in MVC4 and would like to save the PDF file to the file system at the same time I return the view.
The following line of code in the controller action is calling the view:
return new PdfResult(claims, "PDF");
I was able to finally write the pdf to the directory system by changing the code base of the RazorPDF render method. The Rendor method creates a PdfWriter object that is associated to the response stream:
// Associate output with response stream
var pdfWriter = PdfWriter.GetInstance(document, viewContext.HttpContext.Response.OutputStream);
pdfWriter.CloseStream = false;
The solution was to create another PdfWriter object that was associated to a FileStream object as illustrated below:
// Create the pdf file in the directory system
var fileStream = new FileStream(myPdfFilePath, FileMode.Create);
var pdfWriter2 = PdfWriter.GetInstance(document, fileStream);
I then closed the objects:
fileStream.Close();
pdfWriter.Close();
pdfWriter2.Close();
I had to essentially incorporate the PdfResult and PdfView classes of RazorPDF into my own project and significantly alter the code. The reason is because I also had to encorporate calls to an email class that sent the pdf to a user.
The full Render method is displayed below:
public void Render(ViewContext viewContext, TextWriter writer)
{
// generate view into string
var sb = new System.Text.StringBuilder();
TextWriter tw = new System.IO.StringWriter(sb);
myResult.View.Render(viewContext, tw);
var resultCache = sb.ToString();
// detect itext (or html) format of response
XmlParser parser;
using (var reader = GetXmlReader(resultCache))
{
while (reader.Read() && reader.NodeType != XmlNodeType.Element)
{
// no-op
}
if (reader.NodeType == XmlNodeType.Element && reader.Name == "itext")
parser = new XmlParser();
else
parser = new HtmlParser();
}
// Create a document processing context
var document = new Document();
document.Open();
// Associate output with response stream
var pdfWriter = PdfWriter.GetInstance(document, viewContext.HttpContext.Response.OutputStream);
pdfWriter.CloseStream = false;
// Create the pdf file in the directory system
var fileStream = new FileStream(myPdfFilePath, FileMode.Create);
var pdfWriter2 = PdfWriter.GetInstance(document, fileStream);
// this is as close as we can get to being "success" before writing output
// so set the content type now
viewContext.HttpContext.Response.ContentType = "application/pdf";
// parse memory through document into output
using (var reader = GetXmlReader(resultCache))
{
parser.Go(document, reader);
}
fileStream.Close();
// Send an email to the claimant
Thread.Sleep(100);
if (File.Exists(myPdfFilePath))
{
var subject = "PDF Documents";
var body = Config.GetContent(ContentParams.CLAIM_DOCUMENT_EMAIL_BODY_TEXT);
bool success;
string errorMessage;
Email.Send(myEmailAddress, subject, body, out success, out errorMessage, myPdfFilePath);
}
pdfWriter.Close();
pdfWriter2.Close();
}
It would be nice if this capability were somehow incorporated into the current RazorPDF project.
why not just get the stream via a web request to the url?
string razorPdfUrl="http://...";
var req = HttpWebRequest.Create(RazorPDFURL);
using (Stream pdfStream = req.GetResponse().GetResponseStream())
{
...
}
this is my controller class:-
public class PlantHeadController : Controller
{
private WOMSEntities2 db = new WOMSEntities2();
//
// GET: /PlantHead/
Document doc = new Document();
static String[] tt=new String[20];
public ActionResult Index()
{
ViewBag.productCode = new SelectList(db.Product, "ID","code");
return View();
}
public void Convert()
{
PdfWriter.GetInstance(doc, new
FileStream((Request.PhysicalApplicationPath + "\\Receipt3.pdf"),
FileMode.Create));
doc.Open();
PdfPTable table = new PdfPTable(2);
doc.AddCreationDate();
PdfPCell cell = new PdfPCell(new Phrase("Receipt"));
cell.Colspan = 3;
cell.HorizontalAlignment = 1; //0=Left, 1=Centre, 2=Right
table.AddCell(cell);
table.AddCell("ahym");
table.AddCell("ram";
table.AddCell("good");
table.AddCell("morning");
String rawGroup = "";
foreach (String lll in raw)
rawGroup = rawGroup + lll+" ";
table.AddCell("" + rawGroup);
doc.Add(table);
doc.Close();
Response.Redirect("~/Receipt3.pdf");
}
}
whenever i press submit button to make pdf file then this error window is opened:-
means pdf is not generated successfully. in some cases old pdf is shown. please suggest me what should i do?
Everything looks good for the most part above (except a missing parenthesis on table.AddCell("ram"; which I assume is just a typo and you could also do with some using statements). I don't know why you would get an error but the reason that you're getting the same PDF is almost definitely because of browser caching. You could append a random querystring to the file but I'd recommend instead skipping the file completely and writing the binary stream directly. This way you can control the caching and you don't have to work about browser redirection. The below code should work for you (its targeting 5.1.1.0 depending on your version you may or may not be able to use some of the using statements).
EDIT
I donwgraded my code to not use the IDisposable interfaces found in newer versions, this should work for you now. (I don't have access to a C# compiler so I didn't test it so hopefully this works.)
using (MemoryStream ms = new MemoryStream())
{
Document doc = new Document());
PdfWriter writer = PdfWriter.GetInstance(doc, ms));
doc.Open();
doc.AddCreationDate();
PdfPTable table = new PdfPTable(2);
PdfPCell cell = new PdfPCell(new Phrase("Receipt"));
cell.Colspan = 3;
cell.HorizontalAlignment = 1; //0=Left, 1=Centre, 2=Right
table.AddCell(cell);
table.AddCell("ahym");
table.AddCell("ram");
table.AddCell("good");
table.AddCell("morning");
String rawGroup = "";
foreach (String lll in raw)
{
rawGroup = rawGroup + lll + " ";
}
table.AddCell("" + rawGroup);
doc.Add(table);
doc.Close();
Response.Clear();
Response.ContentType = "application/pdf";
Response.AddHeader("content-disposition", "attachment;filename=Receipt3.pdf");
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.BinaryWrite(ms.ToArray());
System.Web.HttpContext.Current.ApplicationInstance.CompleteRequest();
}