I'm building an website that allows users to upload/download/delete/manage files in a database.
For clarification, the web application uploads files to a database, NOT the file system. This is because of server constraints and I have no control over it.
I use a filestream to convert the file into a blob and then stick it in the database. What I'd like to know is:
Is it possible to get the progress of a filestream for large files? See how much has been streamed so far or set a timer to update that value?
My code is as follows:
Dim fs As Stream = upload1.PostedFile.InputStream
Dim br As New BinaryReader(fs)
Dim bytes as Byte() = br.ReadBytes(fs.Length)
Dim length As Integer = fs.Length
Then I add "bytes" as a parameter to my Stored Procedure and run the query. What could I add to this to maybe get the status of the filestream?
I hope this is clear, if not I can clarify.
Thanks!
You could copy one stream to another using a code like this and insert your progress code in the middle:
Public Shared Sub Copy(ByVal source As Stream, ByVal destination As Stream, ByVal bufferSize As Integer)
Dim num As Integer
Dim buffer As Byte() = New Byte(bufferSize - 1) {}
Do While (num = source.Read(buffer, 0, buffer.Length) <> 0)
destination.Write(buffer, 0, num)
' insert progress code here
Loop
End Sub
Related
I have a System.Web.UI.WebControls.FileUpload control that passes both Word and PDF files that need to be stored in Azure Blob Storage.
From the Code Behind page it passes to the common library to manage Azure Functions:
Private Sub UploadButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles UploadButton.Click
Dim fileExt As String = String.Empty
Dim newGuid As New Guid
Dim fileName As String
Dim documentType As Document.DocumentType
Page.Validate()
newGuid = Guid.NewGuid
If Page.IsValid() AndAlso Me.FileUploadNewDoc.HasFile Then
Try
'Test that MIME is either msword of pdf
If FileUploadNewDoc.PostedFile.ContentType.Contains("msword") Then
fileExt = "doc"
documentType = Document.DocumentType.LeaseWordDoc
ElseIf FileUploadNewDoc.PostedFile.ContentType.Contains("pdf") Then
fileExt = "pdf"
documentType = Document.DocumentType.LeasePDF
Else
fileExt = "na"
End If
If fileExt <> "na" Then
fileName = newGuid.ToString & "." & fileExt
AzureStorage.SaveBlob(FileUploadNewDoc.FileContent, fileName, mDocumentContainer, mStorageConnectionString)
End If
Catch ex As Exception
' Handle Error
Finally
FileUploadNewDoc.Dispose()
End Try
End If
End Sub
The AzureStorage.SaveBlob code:
Public Function SaveBlob(ByRef fileContent As Stream,
ByVal fileName As String,
ByVal containerName As String,
ByVal storageConnectionString As String) As Boolean
Dim storageAccount As CloudStorageAccount = CloudStorageAccount.Parse(storageConnectionString)
Dim blobClient As CloudBlobClient = storageAccount.CreateCloudBlobClient()
Dim container As CloudBlobContainer = blobClient.GetContainerReference(containerName)
Dim blockBlob As CloudBlockBlob = container.GetBlockBlobReference(fileName)
Using fileContent
fileContent.Position = 0
blockBlob.UploadFromStream(fileContent)
End Using
Return True
End Function
My questions:
Is this best way to take the File that has been uploaded and save it to Azure Blob Storage?
Am I handling the Stream correctly? I'm passing ByRef and have a Using statement around the usage.
Should I be setting content type explicitly when saving it to storage? If so how do I do that?
Note, I normally code in C# so an example in C# is fine if you're not familiar with VB.NET.
Is this best way to take the File that has been uploaded and save it
to Azure Blog Storage?
The best way depends on your use case. If it is just small files you're OK. If you want to support large files you might want to do chunked uploading. You can take blocks of 1 megabyte which you can upload separately or in parallel. Once you are done uploading all the blocks you commit the file and it is stiched together in Azure Blob storage. Look at CloudBlockBlob.PutBlock and CloudBlockBlob.PutBlockList.
Am I handling the Stream correctly? I'm passing ByRef and have a Using
statement around the usage.
You are but if you want to support larger files you might want to upload with JavaScript and create two endpoint to receive chunks and to commit after all chunks are sent. There are multiple libraries that can help you.
Should I be setting content type explicitly when saving it to storage? If so
how do I do that?
If you upload files that you want to embed in HTML it's wise to have a content type. If you want the links to the file to be download links you don't have to. Although it can never hurt.
blockBlob.Properties.ContentType = "image/jpeg";
I have tired many things, searched the internet, and still I cannot figure out what is going on with this code. I still get that my docx files are corrupted, but when I do it with doc file everything is going great.
My Upload Code
Private Sub LbReqUploadAttachment1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles LbReqUploadAttachment1.Click
If FileUplReqAttachment1.HasFile Then
'Then save the attachment to the documents table
Dim type As String = Me.FileUplReqAttachment1.PostedFile.ContentType
Dim myFile As System.Web.HttpPostedFile = Me.FileUplReqAttachment1.PostedFile
Dim nFileLen As Integer = myFile.ContentLength
Dim myData(nFileLen) As Byte
myFile.InputStream.Read(myData, 0, nFileLen)
Dim DocDto As New DocumentsDto
DocDto.Month = Now.ToString("m")
DocDto.Year = Now.Year
DocDto.MimeType = type
DocDto.UploadedById = MyPage.LoggedOnUser.DtoUser.PersonId
DocDto.DocumentBytes = myData.ToArray
DocDto = MyPage.DelegateDocument.CreateDocumentsDto(DocDto)
'Update the order with the new document id
If Me.TbIntlFlagz.Checked Then
Item.AssetID = CStr(DocDto.DocumentID)
Else
Item.AssetID = "0"
End If
' Save Everything
SaveItem()
'Focus after postback
FileUplReqAttachment1.Focus()
End If
'Stay on order screen
Response.Redirect(String.Format("Default.aspx?i={0}&Item={1}", MyPage.DtoPage.PageID, Me.Item.Id))
End Sub
My download code:
Sub ProcessRequest(ByVal context As HttpContext) Implements ttpHandler.ProcessRequest
Dim docba As Byte() = docDto.DocumentBytes
Dim ext As String = Mime.GetExtensionFromMime(docDto.MimeType)
context.Response.ContentType = docDto.MimeType
If String.IsNullOrEmpty(ext) Then
'We can only use the attachment approach if we found a good extension based on the mime type
Else
Dim DispositionHeader As String
If Not context.Request.QueryString.Item("fn") Is Nothing Then
DispositionHeader = String.Format("attachment; filename={0}.{1}", AntiXss.UrlEncode(context.Request.QueryString.Item("fn")), ext)
Else
DispositionHeader = String.Format("attachment; filename={0}.{1}", AntiXss.UrlEncode("Document"), ext)
End If
context.Response.AppendHeader("Content-Disposition", DispositionHeader)
End If
context.Response.Expires = (60 * 24 * 1)
context.Response.OutputStream.Write(docba, 0, docba.Length)
context.Response.Flush()
docba = Nothing
End If
End Sub
I have tired these with no success:
Why are .docx files being corrupted when downloading from an ASP.NET page?
http://www.aspmessageboard.com/showthread.php?230778-Downloaded-docx-files-are-corrupted
https://social.msdn.microsoft.com/Forums/vstudio/en-US/88383fb2-03c6-49f5-afee-ce38497789bd/retrieving-docx-stored-in-sql-server-results-in-there-was-an-error-opening-the-file?forum=vbgeneral
I am uploading the file into a DB, and downloading the file by pressing a hyperlink. When I press the hyperlink and download the file. View at the file it is corrupted.
In VB, when declaring an array you give it the number of elements in the array. This is different from many languages where you specify the last index of the array.
For the code you show, you need to use
Dim myData(nFileLen - 1) As Byte
to make sure that you do not have an extra element in the array.
It appears that the .doc format is not sensitive to this, but .docx is.
I have a Telerik RadBinaryImage control which is displaying an image loaded using Telerik RadAsyncUpload.
I have an SQL table containing a 'Photo' column (image data type). I want to save to the database the image from the Telerik RadBinaryImage to the insert function which look like this:
Private Sub InsertPhotoIntoDB()
Dim sMyConn As String = My.Settings.appDBConnString
Dim myConnection As SqlConnection
Dim myCommand As New SqlCommand
myConnection = New SqlConnection(sMyConn)
myConnection.Open()
myCommand = New SqlCommand("INSERT INTO Photos(Photo) VALUES(#Photo)")
myCommand.Connection = myConnection
myCommand.Parameters.Add("#Photo", SqlDbType.Image, 0, "Photo")
myCommand.Parameters("#Photo").Value = WhatDoIPutHere???
myCommand.ExecuteNonQuery()
myConnection.Close()
myConnection.Dispose()
End Sub
I've tried:
myCommand.Parameters.Add("#Photo", SqlDbType.Image).Value = RadBinaryImage1.DataValue
but I still get an error:
The parameterized query '(#Photo image)INSERT INTO Photos (Photo) VALUES (#Photo)' expects parameter '#Photo', which was not supplied.
Do I need to convert RadBinaryImage1.DataValue to image?
The RadBinaryImage control is used to display an image coming from the database. So, the sequence should be like this:
User uploads photo via RadUpload
Image stored in database using modified code (see below)
On PostBack, the page displays the image from the database using the RadBinaryImage
If you don't want to store in the database right away, you will need to persist the data yourself in some way (Could store the file on the filesystem and then access the path via RadBinaryImage.SavedImageName, or persist in memory as Session["UploadedImage"]). The bottom line is RadBinaryImage is only meant to display an image on the page.
With regards to your sample code, I recommend using the SQL column type varbinary instead of image.
Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles Button1.Click
For Each file As UploadedFile In RadUpload1.UploadedFiles
Dim bytes(file.ContentLength - 1) As Byte
file.InputStream.Read(bytes, 0, file.ContentLength)
Dim connection As OleDbConnection = CreateConnection()
Try
Dim command As New OleDbCommand("INSERT INTO Images ([Name], [Size], [Content]) VALUES (?, ?, ?)", connection)
command.Parameters.AddWithValue("#Name", file.GetName())
command.Parameters.AddWithValue("#Size", bytes.Length)
command.Parameters.AddWithValue("#Content", bytes)
connection.Open()
command.ExecuteNonQuery()
Finally
If connection.State = Data.ConnectionState.Open Then
connection.Close()
End If
End Try
Next
End Sub
See this post for sample code and more explanation:
http://www.telerik.com/help/aspnet-ajax/upload-manipulating-files.html
The safest way is to upload the image as a byte array:
Byte[] yourByteArray;
...
myCommand.Parameters.AddWithValue("#Photo", yourByteArray);
How to get to a byte array differs. With the ASP.NET Upload control, it's just:
yourByteArray = FileUpload1.FileBytes;
Or if the image is represented as a stream:
var yourByteArray = new Byte[ImageObject.InputStream.Length];
ImageObject.InputStream.Read(yourByteArray, 0, yourByteArray.Length);
If Request.QueryString("ID") = "" Then
folderDirectory = Global.FileUpload.GetFolderDirectory(Request.QueryString("TFID"))
If Not File.Exists(folderDirectory + fileName) Then
If Not Directory.Exists(folderDirectory) Then
Directory.CreateDirectory(folderDirectory)
End If
Dim bufferSize As Integer = Me.fileUpload.PostedFile.ContentLength
Dim buffer As Byte() = New Byte(bufferSize) {}
' write the byte to disk
Using fs As New FileStream(Path.Combine(folderDirectory, fileName), FileMode.Create)
Dim bytes As Integer = Me.fileUpload.PostedFile.InputStream.Read(buffer, 0, bufferSize)
' write the bytes to the file stream
fs.Write(buffer, 0, bytes)
End Using
Else
CallOnComplete("error", "", "Error uploading '" & fileName & "'. File has been exists!")
Exit Sub
End If
But Fortify scan report for the above sample code shows Path Manipulation issue as high. I Need help to modify above code so that it can pass fortify scan
It is showing me error at folderDirectory
Usually, when your code works inside a web application you don't have the liberty to use the full file system as you do on your local PC. Any kind of 'Path Manipulation' is suspicious.
You should try to recode your works using Server.MapPath method.
Pay particular attention to this warning
For security reasons, the AspEnableParentPaths property has a default value set to FALSE.
Scripts will not have access to the physical directory structure unless AspEnableParentPaths
is set to TRUE.
I've been reading gobs of articles on FTP upload in ASP.NET recently and they all seem to make sense, but every time I've tried implementing them I either get an empty file uploaded, or no file at all. Here are some of the articles I've been reading:
Managing FTP Transfers from an ASP.NET Web Page By John Peterson
FileUpload Control Doesn’t Give Full Path….HELP!!!!
How to: Upload Files with the FileUpload Web Server Control
They're all great articles, but like I said, having issues :(
I know exactly what the problem is but I don't know how to fix it. I can pass the file name from the FileUpload control, but the path does not exist for security concerns. However, the StreamReader object requires the fully qualified path of the file to be uploaded, so how the heck do I get that? I'm at my wits end! >.<
Let's use the example by John Peterson that I linked above. Here's the code:
Protected Sub btnUploadFile_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Dim myFtpWebRequest As FtpWebRequest
Dim myFtpWebResponse As FtpWebResponse
Dim myStreamWriter As StreamWriter
myFtpWebRequest = WebRequest.Create("ftp://ftp_server_name/filename.ext")
myFtpWebRequest.Method = WebRequestMethods.Ftp.UploadFile
myFtpWebRequest.UseBinary = True
myStreamWriter = New StreamWriter(myFtpWebRequest.GetRequestStream())
'IT BREAKS HERE BECAUSE THE CLIENT PATH IS WRONG!!
myStreamWriter.Write(New StreamReader(Server.MapPath("filename.ext")).ReadToEnd)
myStreamWriter.Close()
myFtpWebResponse = myFtpWebRequest.GetResponse()
myFtpWebResponse.Close()
End Sub
See? No data in the uploaded file :(
Now my latest implementation looks like this, but the uploaded file is much larger than the source, and corrupted. Seriously, what the heck am I doing wrong? I've been two LONG days at this, grrr...
Protected Sub btnUploadFile2_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Dim myFtpWebRequest As FtpWebRequest
Dim myFtpWebResponse As FtpWebResponse
filename = Path.GetFileName(FileUpload1.FileName)
myFtpWebRequest = CType(WebRequest.Create(ftpServer + ftpPath + filename), FtpWebRequest)
myFtpWebRequest.Method = WebRequestMethods.Ftp.UploadFile
myFtpWebRequest.UseBinary = True
'NEW APPROACH USING THE STREAM OF THE FILE FROM THE FileUpload Control
'CORRECT BYTE LENGTH - in sourceStream.BaseStream
Dim sourceStream As New StreamReader(FileUpload1.FileContent)
'WRONG BYTE LENGTH - in sourceStream.ReadToEnd()
Dim fileContents As Byte() = Encoding.UTF8.GetBytes(sourceStream.ReadToEnd())
sourceStream.Close()
myFtpWebRequest.ContentLength = fileContents.Length
Dim requestStream As Stream = myFtpWebRequest.GetRequestStream()
requestStream.Write(fileContents, 0, fileContents.Length)
requestStream.Close()
myFtpWebResponse = CType(myFtpWebRequest.GetResponse(), FtpWebResponse)
myFtpWebResponse.Close()
End Sub
Thanks ever so much to Adam Maras for the amazing answer. I'll leave my blunders here for others to benefit who find this thread ;)
First of all, you must upload through the web server if you're going to use ASP.NET like this. Without installing a plugin on the client's browser or using an ActiveX control (or similar) you absolutely cannot upload directly from the client machine to the FTP server.
I assume you're uploading binary files; if that's the case, the way you're using StreamReaders and StreamWriters could be corrupting the binary contents of the file. Instead, we can use the Stream.CopyTo method to move the data verbatim from one stream to the other.
I've modified your method to use this pattern instead:
Protected Sub btnUploadFile2_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Dim myFtpWebRequest As FtpWebRequest
Dim myFtpWebResponse As FtpWebResponse
filename = Path.GetFileName(FileUpload1.FileName)
myFtpWebRequest = CType(WebRequest.Create(ftpServer + ftpPath + filename), FtpWebRequest)
myFtpWebRequest.Method = WebRequestMethods.Ftp.UploadFile
myFtpWebRequest.UseBinary = True
Dim myFileStream As Stream = FileUpload1.FileContent
myFtpWebRequest.ContentLength = myFileStream.Length
Dim requestStream As Stream = myFtpWebRequest.GetRequestStream()
myFileStream.CopyTo(requestStream)
requestStream.Close()
myFtpWebResponse = CType(myFtpWebRequest.GetResponse(), FtpWebResponse)
myFtpWebResponse.Close()
End Sub
The FileUpload.SaveAs() method saves to the Web server's local file system, and can't write to a URI or FTP site. To do that, you'll need to create a WebRequest.
See the MSDN reference for the FileUpload control here: http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.fileupload.saveas.aspx
and for the FTP use of a WebRequest here: http://msdn.microsoft.com/en-us/library/ms229715.aspx
Note the example given in the FileUpload documentation saves to c:\temp\uploadedfiles. I'd suggest you use Path.GetTempFileName() instead as this is guaranteed to give you a file that can always be written no matter what environment you're under.
The data gets corrupted because you are reading the file as if it was text, but it's not.
Use a BinaryReader instead of a StreamReader so that you can read the data as bytes directly:
Dim fileContents As Byte()
Using sourceStream As New BinaryReader(FileUpload1.FileContent)
fileContents = sourceStream.ReadBytes(Int32.MaxValue)
End Using