Writing to FileStream works, MemoryStream copied to FileStream doesn't - vb.net

I have some code that used a FileStream, StreamWriter and XmlDocument to produce Excel-compatible output files. Very useful!
However I now have a need to make copies of the file, and I'd like to do that in-memory. So I took my original FileStream code and changed the FileStream to a MemoryStream, and then wrapped that in this function:
'----------------------------------------------------------------------------------
Friend Sub Save(Optional ByVal SaveCalculatedResults As Boolean = True)
Dim MStream As MemoryStream
Dim FStream As FileStream
Dim Bytes As Byte()
'make the stream containing the XML
MStream = ToXLSL(SaveCalculatedResults)
If MStream.Length = 0 Then Return
'then read that data into a byte buffer
ReDim Bytes(CInt(MStream.Length))
MStream.Read(Bytes, 0, CInt(MStream.Length))
'and then write it to "us"
FStream = New FileStream("C:\OUTFILE.XLSX", FileMode.Create)
FStream.Write(Bytes, 0, CInt(MStream.Length))
FStream.Flush()
End Sub
This creates a file in the correct location, it has the exact same length as it did before, but opening it in Excel causes an error about the file format being invalid.
Can anyone see any obvious problems in that code? Perhaps I am writing the bytes backwards? Is this possibly a text encoding problem? 32/64 problem?
p.s. I tried using CopyTo, but that doesn't seem to work in VB?

It requires guessing what ToXLSL() does but the behavior gives a strong hint: the MemoryStream's Position is located at the end of the stream. So the Read() call doesn't actually read anything. Verify by checking its return value.
Just get rid of Bytes() entirely, it is very wasteful to duplicate the data like this. You don't need it, the MemoryStream already gives you access to the data:
Using FStream = New FileStream("C:\OUTFILE.XLSX", FileMode.Create)
FStream.Write(MStream.GetBuffer(), 0, CInt(MStream.Length))
End Using
Do note that the Using statement is not optional. And that you cannot write to C:\

Related

VB.NET: Modifying non-text file as text without ruining it

I need my application to find and modify a text string in a .swp file (generated by VBA for SOLIDWORKS). If I open said file as text in Notepad++, most of the text looks like this (this is an excerpt):
Meaning there is readable text, and symbols that appear as NUL, BEL, EXT and so on, depending on selected encoding. If I make my changes via Notepad++ (finding and changing "1.38" to "1.39"), there are no issues, the file can be opened via SOLIDWORKS and is still recognized as valid. After all, I don't need to modify these non-readable bits. However, if I do the same modification in my VB.NET application,
Dim filePath As String = "D:\OneDrive\Desktop\launcher macro.swp"
Dim fileContents As String = My.Computer.FileSystem.ReadAllText(filePath, Encoding.UTF8).Replace("1.38", "1.39")
My.Computer.FileSystem.WriteAllText(filePath, fileContents, Encoding.UTF8)
then the file gets corrupted, and is no longer recognized by SOLIDWORKS. I suspect this is because ReadAllText and WriteAllText cannot handle whatever data is in these non-readable bits.
I tried many different encodings, but it seems to make no difference. I am not sure how Notepad++ does it, but I can't seem to get the same result in my VB.NET application.
Can someone advise?
Thanks to #jmcilhinney, this is a solution that worked for me - reading file as bytes, converting to string, and then saving, using ANSI formatting:
Dim file_name As String = "D:\OneDrive\Desktop\launcher macro.swp"
Dim fs As New FileStream(file_name, FileMode.Open)
Dim binary_reader As New BinaryReader(fs)
fs.Position = 0
Dim bytes() As Byte = binary_reader.ReadBytes(binary_reader.BaseStream.Length)
Dim fileContents As String = System.Text.Encoding.Default.GetString(bytes)
fileContents = fileContents.Replace("1.38", "1.39")
binary_reader.Close()
fs.Dispose()
System.IO.File.WriteAllText(file_name, fileContents, Encoding.Default)

Extract text from a PDF email attachment without saving the attachment to a pdf file first

I'm using PDF Extractor (from here) to get the text from PDF attachments in emails.
It seems to me that the only way I can extract the text is to save the PDF to a file, and then using the code.
Private Function ReadPdfToStringList(tempfilename As String) As List(Of String)
Dim extractedText As String
Using pdfFile As FileStream = File.OpenRead(tempfilename)
Using extractor As Extractor = New Extractor()
extractedText = extractor.ExtractToString(pdfFile)
End Using
End Using
DeleteTempFile()
Return New List(Of String)(extractedText.Split(Chr(13)))
End Function
to extract a list of Strings from the PDF file.
However, I cant seem to extract text from the attachment directly. The 'extractor' doesnt seem to be able to handle any source other than a file on disk.
Is there any possible way of either tricking the 'extractor' into opening a file from memory maybe by creating an in memory file stream?
I've tried using a MemoryStream like this:
Private Function ReadPdfMemStrmToStringList(memstream As MemoryStream) As List(Of String)
Dim extractedText As String
Using extractor As Extractor = New Extractor()
extractedText = extractor.ExtractToString(memstream)
End Using
Return New List(Of String)(extractedText.Split(Chr(13)))
End Function
but because the extractor is assuming the source is a disk file, it returns an error saying that it cant find a temporary file.
To be honest I've spent quite a bit of time trying to understand memory streams and they don't seem to fit the bill.
UPDATE
Here also is the code that I'm using to save the attachment to the MemoryStream.
Private Sub SaveAttachmentToMemStrm(msg As MimeMessage)
Dim memstrm As New MemoryStream
For Each attachment As MimePart In msg.Attachments
If attachment.FileName.Contains("booking") Then
attachment.WriteTo(memstrm)
End If
Next
'this line only adds the memory stream to a List (of MemoryStream)
attachments.Add(memstrm)
End Sub
Many apologies if I've missed something obvious.

How do you delete a file generated via webapi after returning the file as response?

I'm creating a file on the fly on a WebAPI call, and sending that file back to the client.
I think I'm misunderstanding flush/close on a FileStream:
Dim path As String = tempFolder & "\" & fileName
Dim result As New HttpResponseMessage(HttpStatusCode.OK)
Dim stream As New FileStream(path, FileMode.Open)
With result
.Content = New StreamContent(stream)
.Content.Headers.ContentDisposition = New Headers.ContentDispositionHeaderValue("attachment")
.Content.Headers.ContentDisposition.FileName = fileName
.Content.Headers.ContentType = New Headers.MediaTypeHeaderValue("application/octet-stream")
.Content.Headers.ContentLength = stream.Length
End With
'stream.Flush()
'stream.Close()
'Directory.Delete(tempFolder, True)
Return result
You can see where I've commented things out above.
Questions:
Does the stream flush/close itself?
How can I delete the tempFolder after returning the result?
On top of all this, it would be great to know how to generate the file and send it to the user without writing it to the file system first. I'm confident this is possible, but I'm not sure how. I'd love to be able to understand how to do this, and solve my current problem.
Update:
I went ahead with accepted answer, and found it to be quite simple:
Dim ReturnStream As MemoryStream = New MemoryStream()
Dim WriteStream As StreamWriter = New StreamWriter(ReturnStream)
With WriteStream
.WriteLine("...")
End With
WriteStream.Flush()
WriteStream.Close()
Dim byteArray As Byte() = ReturnStream.ToArray()
ReturnStream.Flush()
ReturnStream.Close()
Then I was able to stream the content as bytearraycontent:
With result
.Content = New ByteArrayContent(byteArray)
...
End With
On top of all this, it would be great to know how to generate the file and send it to the user without writing it to the file system first. I'm confident this is possible, but I'm not sure how. I'd love to be able to understand how to do this, and solve my current problem.
To do the same thing without writing a file to disk, you might look into the MemoryStream class. As you'd guess, it streams data from memory like the FileStream does from a file. The two main steps would be:
Take your object in memory and instead of writing it to a file, you'd serialize it into a MemoryStream using a BinaryFormatter or other method (see that topic on another StackOverflow Q here: How to convert an object to a byte array in C#).
Pass the MemoryStream to the StreamContent method, exactly the same way you're passing the FileStream now.

VB.Net Reading Resource Binary File

I have a resource in named StoreCode, I can't seem to read the file using:
Dim readBinaryFile As BinaryReader
readBinaryFile = New BinaryReader(My.Resources.StoreCode)
There is an error:
Value of type:'1-dimensional array of Byte' cannot be converted to 'System.IO.Stream'
How do I correctly read the Binary File?
Seems My.Resources.StoreCode is a byte[] for that reason you need to copy it to a MemoryStream object and then use a BinaryReader to read the stream's content.
Using ms As New MemoryStream(My.Resources.StoreCode)
Using readBinaryFile As New BinaryReader(ms)
'read operations
End Using
End Using
I hope it helps.
My.Resources.StoreCode is probably a an array of bytes. Instead, it needs to be a file stream, similar to this:
Dim readBinaryFile As BinaryReader
Dim fs As System.IO.Stream = File.Open(pathstring, FileMode.Open)
readBinaryFile = New BinaryReader(fs)

Stream Reader and Writer Conflict

I am making a class that is to help with saving some strings to a local text file (I want to append them to that file and not overwrite so that it is a log file). When I write with the streamwriter to find the end of the previous text, I get an error "the file is not available as it is being used by another process". I looked into this problem on MSDN and I got very little help. I tried to eliminate some variables so I removed the streamreader to check was that the problem and it was. When I tried to write to the file then it worked and I got no error so this made me come to the conclusion that the problem arose in the streamreader. But I could not figure out why?
Here is the code:
Public Sub SaveFile(ByVal Task As String, ByVal Difficulty As Integer, ByVal Time_Taken As String)
Dim SW As String = "C:/Program Files/Business Elements/Dashboard System Files/UserWorkEthic.txt"
Dim i As Integer
Dim aryText(3) As String
aryText(0) = Task
aryText(1) = Difficulty
aryText(2) = Time_Taken
Dim objWriter As System.IO.StreamWriter = New System.IO.StreamWriter(SW, True)
Dim reader As System.IO.StreamReader = New System.IO.StreamReader(SW, True)
reader.ReadToEnd()
reader.EndOfStream.ToString()
For i = 0 To 3
objWriter.WriteLine(aryText(reader.EndOfStream + i))
Next
reader.Close()
objWriter.Close()
End Sub
As Joel has commented on the previous answer it is possible to change the type of locking.
Otherwise building on what Neil has suggested, if to try to write to a file with a new reader it is difficult not to lose the information already within the file.
I would suggest you rename the original file to a temporary name, "UserWorkEthicTEMP.txt" for example. Create a new text file with the original name. Now; read a line, write a line, between the two files, before adding your new data onto the end. Finally Delete the temporary file and you will have the new file with the new details. If you have an error the temporary file will serve as a backup of the original. Some sample code below:
Change file names
Dim Line as string
line=Reader.readline
Do until Line=nothing
objwriter.writeline(line)
line=reader.readline
loop
add new values on the end and remove old file
You are trying to read and write to the same file and this is causing a lock contention. Either store the contents of the file into a variable and then write it back out including your new data to the file.
Psuedo
Reader.Open file
String content = Reader.ReadToEnd()
Reader.Close
Writer.Open file
Loop
Writer.Write newContent
Writer.Close