How do I keep a stream open from model to controller? - c#

I am editing a pdf then print in a new tab. I am getting an error saying I "Cannot access a closed stream." This is what I have thus far...
CONTROLLER
public ActionResult QST(string ID)
{
...
PdfReader reader = new PdfReader(Server.MapPath("~/Content/PDF/QST.pdf"));
QstRepository repo = new QstRepository();
Stream newPdf = repo.GS(ID, reader);
return new FileStreamResult(newPdf, "application/pdf");
}
MODEL
public Stream GS(string ID, PdfReader reader)
{
Stream newPdf = new MemoryStream();
using (MemoryStream ms = new MemoryStream())
{
PdfStamper formFiller = new PdfStamper(reader, ms);
AcroFields formFields = formFiller.AcroFields;
formFields.SetField("ID", ID);
formFiller.FormFlattening = true;
formFiller.Writer.CloseStream = false;
newPdf = ms;
formFiller.Close();
}
return newPdf;
}
How do I access the pdf before it is closed... or how do I access the pdf after?

The last two lines of your code before you return are
newPdf = ms;
formFiller.Close();
Since your code is all within a using (MemoryStream ms = new MemoryStream()) block, the MemoryStream ms will be closed before you return anyway.
Since you are assigning newPdf to the variable ms, nwePdf eventualy returns the same (already closed) MemoryStream that ms points to.
You could have public Stream GS(string ID, PdfReader reader) just create the stream without the using block, but then you run the risk of it staying open if an exception occurs in your code. I suppose you could try the following:
// All calls to GS() must ensure that the returned stream gets closed.
public Stream GS(string ID, PdfReader reader)
{
MemoryStream newPdf = new MemoryStream();
PdfStamper formFiller = null;
try
{
formFiller = new PdfStamper(reader, newPdf);
AcroFields formFields = formFiller.AcroFields;
formFields.SetField("ID", ID);
formFiller.FormFlattening = true;
//formFiller.Writer.CloseStream = false;
}
catch
{
// Only close newPdf on an exception
newPdf.Close();
throw; // Rethrow original exception
}
finally
{
// Always ensure that formFiller gets closed
formFiller.Close();
}
return newPdf;
}
Update: I removed the extra MemoryStream, since the one was simply being declared at the beginning of the function, and then set to point to the other at the end, without ever being used for anything else in between.
Update: I found an example using the PdfStamper. According to the example, calling formFiller.Close() is what actually writes to the stream. In their example, they do not set the Writer.CloseStream property to false. That may be why you're getting a blank page. Unfortunately, I was not able to find any actual documentation on PdfStamper to verify this.

In your code, ms and newPdf refer to the same MemoryStream instance; since ms is closed at the end of the using block, newPdf is closed too (since it's the same stream). So you need to return the Stream without closing it:
public Stream GS(string ID, PdfReader reader)
{
PdfStamper formFiller = new PdfStamper(reader, ms);
AcroFields formFields = formFiller.AcroFields;
formFields.SetField("ID", ID);
formFiller.FormFlattening = true;
formFiller.Writer.CloseStream = false;
formFiller.Close();
return ms;
}
I'm not very familiar with ASP.NET MVC, but I assume that FileStreamResult takes care of closing the stream.

Related

Trying to get a memory stream from a pdfstamper into a pdfreader but getting: "PDF startxref not found"

I'm writing an app in c# that fills a bunch of pdf forms, concatenates them then puts in some page numbers. I'm having difficulty with the memorystream result from the pdfstamper. If I change the memorystream to a filestream it works fine but I don't want to use the filesystem. I've created the following code snippet that reproduces my error:
public static void TestStreams(string filepath)
{
PdfReader reader = new PdfReader(filepath);
MemoryStream ms = new MemoryStream();
PdfReader.unethicalreading = true;
PdfStamper stamper = new PdfStamper(reader, ms);
byte[] result = ms.ToArray();
//The error is in the following line
PdfReader reader2 = new PdfReader(result);
}
The error is:
iTextSharp.text.exceptions.InvalidPdfException was unhandled
HResult=-2146232800
Message=Rebuild failed: trailer not found.; Original message: PDF startxref not found.
Source=itextsharp
How can I fix it?
You forgot one line:
public static void TestStreams(string filepath) {
PdfReader reader = new PdfReader(filepath);
MemoryStream ms = new MemoryStream();
PdfReader.unethicalreading = true;
PdfStamper stamper = new PdfStamper(reader, ms);
stamper.Close();
byte[] result = ms.ToArray();
//The error is in the following line
PdfReader reader2 = new PdfReader(result);
}
When you do ms.ToArray() without first closing the stamper, you have an incomplete PDF. The PDF starts with %PDF-, but there's no %%EOF, no trailer, no catalog. An incomplete PDF can't be read by PdfReader.

Trying to use PdfStamper and MemoryStream to add data to existing PDF then email it

Here is my chunk of code. It compiles fine and when I fire off the event I get the email, but I then get this error
Email attachment ERROR on Adobe while opening(Acrobat could not open 'Att00002.pdf' because it is either not a supported file type or because the file has been damaged(for example, it was sent as an email attachment and wasnt correctly decoded.)
string agentName = "My Name";
MemoryStream _output = new MemoryStream();
PdfReader reader = new PdfReader("/pdf/Agent/Specialist_Certificate.pdf");
using (PdfStamper stamper = new PdfStamper(reader, _output))
{
AcroFields fields = stamper.AcroFields;
// set form fields
fields.SetField("FIELD_AGENT_NAME", agentName);
fields.SetField("FIELD_DATE", AvalonDate);
// flatten form fields and close document
stamper.FormFlattening = true;
SendEmail(_output);
DownloadAsPDF(_output);
stamper.Close();
}
private void SendEmail(MemoryStream ms)
{
Attachment attach = new Attachment(ms, new System.Net.Mime.ContentType("application/pdf"));
EmailHelper.SendEMail("myemail#myemail.com", "mjones#globusfamily.com", null, "", "Avalon Cert", "Hope this works", EmailHelper.EmailFormat.Html,attach);
}
EDITED *************************************
using (MemoryStream _output = new MemoryStream())
{
using (PdfStamper stamper = new PdfStamper(reader, _output))
{
AcroFields fields = stamper.AcroFields;
// set form fields
fields.SetField("FIELD_AGENT_NAME", agentName);
fields.SetField("FIELD_DATE", AvalonDate);
// flatten form fields and close document
stamper.FormFlattening = true;
}
SendEmail(_output);
}
You're calling stamper.close() inside the using (PdfStamper stamper = new PdfStamper(reader, _output)). The using will automatically close the stamper upon exiting it in addition to your manual close(), so technically the stamper is trying to be closed twice. Because of this it is also trying to close the MemoryStream more than once. That's where the exception is coming from.
I would use the technique located in the answer here for your MemoryStream and PdfStamper (modified and taken from: Getting PdfStamper to work with MemoryStreams (c#, itextsharp)):
using (MemoryStream _output = new MemoryStream()) {
using (PdfStamper stamper = new PdfStamper(reader, _output)) {
// do stuff
}
}

Getting PdfStamper to work with MemoryStreams (c#, itextsharp)

It came to me to rework old code which signs PDF files into new one, which signs MemoryStreams (byte arrays) that come and are sent by web services. Simple, right? Well, that was yesterday. Today I just can't get it to work.
This is the old code, which uses FileStreams and it works:
public static string OldPdfSigner(PdfReader pdfReader, string destination, string password, string reason, string location, string pathToPfx)
{
using (FileStream pfxFile = new FileStream(pathToPfx, FileMode.Open, FileAccess.Read))
{
...
using (PdfStamper st = PdfStamper.CreateSignature(pdfReader, new FileStream(destination, FileMode.Create, FileAccess.Write), '\0'))
{
PdfSignatureAppearance sap = st.SignatureAppearance;
sap.SetCrypto(key, chain, null, PdfSignatureAppearance.WINCER_SIGNED);
sap.Reason = reason;
sap.Location = location;
return destination;
}
}
}
Below is what I've redone myself which throws System.ObjectDisposedException: Cannot access a closed Stream.
public static byte[] PdfSigner(PdfReader pdfReader, string password, string reason, string location, string pathToPfx)
{
using (FileStream pfxFile = new FileStream(pathToPfx, FileMode.Open, FileAccess.Read))
{
...
MemoryStream outputStream = new MemoryStream();
using (PdfStamper st = PdfStamper.CreateSignature(pdfReader, outputStream, '\0'))
{
st.Writer.CloseStream = false;
PdfSignatureAppearance sap = st.SignatureAppearance;
sap.SetCrypto(key, chain, null, PdfSignatureAppearance.WINCER_SIGNED);
sap.Reason = reason;
sap.Location = location;
st.Close();
outputStream.Position = 0;
return outputStream.ToArray();
}
}
}
and if I comment out
st.Close();
it creates an empty document. What am I doing wrong?
Not specific to your signing code, but when working with MemoryStream and PdfStamper, follow this general pattern:
using (MemoryStream ms = new MemoryStream()) {
using (PdfStamper stamper = new PdfStamper(reader, ms, '\0', true)) {
// do stuff
}
return ms.ToArray();
}
MemoryStream implements IDisposable, so include a using statement.
The PdfStamper using statement takes care of disposing the object, so you don't need to call Close(), and don't need to set the CloseStream property.
Your code snippet is returning the byte array too soon, inside the PdfStamper using statement, so your MemoryStream is effectively a no-op. Return the byte array outside of the PdfStamper using statement, and inside the MemoryStream using statement.
Generally there's no need to reset the MemoryStream Position property.
Ignore the PdfStamper constructor above - it's from some test code I had for filling forms, and use whatever constructor/method you need to do your signing.

Returning memorystream - gives corrupt PDF file or "cannot accessed a closed stream"

I have a web service, which calls the following method. I want to return a memorystream, which is a PDF file.
Now, the problem is the PDF file is corrupt with the following code. I think it's because the files are not being closed. However, if I close them, I get the classic error "Cannot access a closed stream".
When I previously saved it through a filestream, the PDF file wasn't corrupt.
So my humble question is: How to solve it and get back a non-corrupt PDF file? :-)
My code:
public Stream Generate(GiftModel model)
{
var template = HttpContext.Current.Server.MapPath(TemplatePath);
// Magic code which creates a new PDF file from the stream of the other
PdfReader reader = new PdfReader(template);
Rectangle size = reader.GetPageSizeWithRotation(1);
Document document = new Document(size);
MemoryStream fs = new MemoryStream();
PdfWriter writer = PdfWriter.GetInstance(document, fs);
document.Open();
// Two products on every page
int bookNumber = 0;
int pagesWeNeed = (int)Math.Ceiling(((double)model.Books.Count / (double)2));
for (var i = 0; i < pagesWeNeed; i++)
{
PdfContentByte cb = writer.DirectContent;
// Creates a new page
PdfImportedPage page = writer.GetImportedPage(reader, 1);
cb.AddTemplate(page, 0, 0);
// Add text strings
DrawGreetingMessages(model.FromName, model.ReceiverName, model.GiftMessage, cb);
// Draw the books
DrawBooksOnPage(model.Books.Skip(bookNumber).Take(2).ToList(), cb);
// Draw boring shit
DrawFormalities(true, model.GiftLink, cb);
bookNumber += 2;
}
// Close off our streams because we can
//document.Close();
//writer.Close();
reader.Close();
fs.Position = 0;
return fs;
}
Reuse of streams can be problematic, especially if you are using an abstraction and you don't quite know what it is doing to your stream. Because of this I generally recommend never passing streams themselves around. If you can by with it, try just passing the raw underlying byte array itself. But if passing streams is a requirement then I recommend still doing the raw byte array at the end and then wrapping that in a new second stream. Try the below code to see if it works.
public Stream Generate(GiftModel model)
{
//We'll dump our PDF into these when done
Byte[] bytes;
using (var ms = new MemoryStream())
{
using (var doc = new Document())
{
using (var writer = PdfWriter.GetInstance(doc, ms))
{
doc.Open();
doc.Add(new Paragraph("Hello"));
doc.Close();
}
}
//Finalize the contents of the stream into an array
bytes = ms.ToArray();
}
//Return a new stream wrapped around our array
return new MemoryStream(bytes);
}

MemoryStream disables reading when returned

In my program, I am basically reading in a file, doing some processing to it, and then passing it back to the main program as a memorystream, which will be handled by a streamreader. This will all be handled by a class beside my main.
The problem is, when I return the memory stream from my method in another class, the "canread" variable is set to false, and thus causes the streamreader initialization to fail.
Below is an example of the problem happening (though in here I'm writing to the memorystream in the other class, but it still causes the same error when i pass it back.
In the class named "Otherclass":
public static MemoryStream ImportantStreamManipulator()
{
MemoryStream MemStream = new MemoryStream();
StreamWriter writer = new StreamWriter(MemStream);
using (writer)
{
//Code that writes stuff to the memorystream via streamwriter
return MemStream;
}
}
The function calls in the main program:
MemoryStream MStream = Otherclass.ImportantStreamManipulator();
StreamReader reader = new StreamReader(MStream);
When I put a breakpoint on the "return MemStream", the "CanRead" property is still set to true. Once I step such that it gets back to my main function, and writes the returned value to MStream, the "CanRead" property is set to false. This then causes an exception in StreamReader saying that MStream could not be read (as the property indicated). The data is in the streams buffer as it should be, but I just can't get it out.
How do I set it so that "CanRead" will report true once it is returned to my main? Or am I misunderstanding how MemoryStream works and how would I accomplish what I want to do?
This is the problem:
using (writer)
{
//Code that writes stuff to the memorystream via streamwriter
return MemStream;
}
You're closing the writer, which closes the MemoryStream. In this case you don't want to do that... although you do need to flush the writer, and rewind the MemoryStream. Just change your code to:
public static MemoryStream ImportantStreamManipulator()
{
// Probably add a comment here stating that the lack of using statements
// is deliberate.
MemoryStream stream = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
// Code that writes stuff to the memorystream via streamwriter
writer.Flush();
stream.Position = 0;
return stream;
}
The StreamWriter takes ownership of the memory stream and when the using statement ends, the MemoryStream is also closed.
See Is there any way to close a StreamWriter without closing its BaseStream?.
As others have stated, the problem is that the Stream is closed when the StreamWriter is closed. One possible way to deal with this is to return a byte array rather than a MemoryStream. This avoids having potentially long running objects that must be disposed by the garbage collector.
public static void Main()
{
OutputData(GetData());
}
public static byte[] GetData()
{
byte[] binaryData = null;
using (MemoryStream ms = new MemoryStream())
using (StreamWriter sw = new StreamWriter(ms))
{
string data = "My test data is really great!";
sw.Write(data);
sw.Flush();
binaryData = ms.ToArray();
}
return binaryData;
}
public static void OutputData(byte[] binaryData)
{
using (MemoryStream ms = new MemoryStream(binaryData))
using (StreamReader sr = new StreamReader(ms))
{
Console.WriteLine(sr.ReadToEnd());
}
}
Another method is to copy the Stream to another stream prior to returning. However, this still has the problem that subsequent access to it with a StreamReader will close that stream.
public static void RunSnippet()
{
OutputData(GetData());
}
public static MemoryStream GetData()
{
MemoryStream outputStream = new MemoryStream();
using (MemoryStream ms = new MemoryStream())
using (StreamWriter sw = new StreamWriter(ms))
{
string data = "My test data is really great!";
sw.Write(data);
sw.Flush();
ms.WriteTo(outputStream);
outputStream.Seek(0, SeekOrigin.Begin);
}
return outputStream;
}
public static void OutputData(MemoryStream inputStream)
{
using (StreamReader sr = new StreamReader(inputStream))
{
Console.WriteLine(sr.ReadToEnd());
}
}

Categories

Resources