ASP.NET Filename encoding while sending file - asp.net

I am sending a file from ASP.NET Page to the browser. To properly send a filename I am adding a header:
Response.ContentType = "application/octet-stream";
Response.AddHeader("Content-Disposition", "attachment; filename=" + filename);
The problem is that when file contains white spaces (e.g. "abc def") browser receives only "abc" part of the filename. I have tried with: Server.HtmlEncode but it didn't help.
Do you have any idea how to solve this problem?
PK

Put the file name in quotes:-
Response.ContentType = "application/octet-stream";
Response.AddHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");

Don't UrlEncode. This is not the right way to escape a value for use in an HTTP structured header parameter. It only works in IE due to that browser's buggy handling, and even then not reliably.
For a space you can use a quoted-string as suggested by Anthony (+1). But the dirty truth of Content-Disposition is that there is no reliable, supported escaping scheme that can be used to put arbitrary characters such as ;, " or Unicode characters in the filename parameter. The only approach that works reliably cross-browser is to drop the filename parameter completely and put the desired filename in the URI as a trailing, UTF-8+URL-encoded path part.
See this answer for some background.

Filename with special symbols(e.g: space; # # ! $ ) or Non-Unicode characters either cannot be supported by some browsers or cause incorrect filename in client machine.
Here is an article by a Chinese called chanext, he gave a perfect way to solve this problem:
this article gave a sample code(written with c#) to show how to get perfect solution to this problem in the all four popular browsers (IE; Opera; Firefox and Chrome)
the filename "Microsoft.Asp.Net.doc" and "F ile;;!#%#^&y.doc" can both be output correctly using the way the author provided in this article.
http://ciznx.com/post/aspnetstreamdownloaddisplaynonunicodespacechar.aspx

Based on the code referenced by #chanext I cleaned it up and put it into a single extension method. Hope this can help someone.
Partial Class Uploader
Inherits Page
Private Sub UploadFile()
Dim sFileName As String
Dim oPdf As MigraDoc.Rendering.PdfDocumentRenderer
sFileName = "File Name With Spaces #22.pdf"
With Me.Request.Browser
If .Browser = "InternetExplorer" OrElse .Browser = "IE" Then
sFileName = sFileName.EncodeForIE
Else
sFileName = String.Format("""{0}""", sFileName)
End If
End With
oPdf = New MigraDoc.Rendering.PdfDocumentRenderer
oPdf.Document = FileFactory.CreatePdf()
oPdf.RenderDocument()
Using oStream As New MemoryStream
oPdf.Save(oStream, False)
Me.Response.Clear()
Me.Response.ContentType = "application/pdf"
Me.Response.AddHeader("content-disposition", String.Format("attachment; filename={0}", sFileName))
Me.Response.AddHeader("content-length", oStream.Length)
Me.Response.BinaryWrite(oStream.ToArray)
End Using
Me.Response.Flush()
Me.Response.End()
End Sub
End Class
Public Module StringExtensions
<Extension()>
Public Function EncodeForIE(Url As String) As String
Dim _
sReservedChars,
sEncodedString As String
sReservedChars = "$-_.+!*'(),#=&"
With New StringBuilder
Url.ToList.ForEach(Sub(C)
If Char.IsLetterOrDigit(C) OrElse sReservedChars.Contains(C) Then
.Append(C)
Else
With New StringBuilder
C.ToBytes.ToList.ForEach(Sub(B)
.AppendFormat("%{0}", Convert.ToString(B, 16))
End Sub)
sEncodedString = .ToString
End With
.Append(sEncodedString)
End If
End Sub)
Return .ToString
End With
End Function
<Extension()>
Public Function ToBytes(Chr As Char) As Byte()
Return Encoding.UTF8.GetBytes(Chr.ToString)
End Function
End Module

Related

How to send multiple files with Response. ASP.NET

I'm trying to call below code in a loop hundreds of times:
Sub ExportReport(ByVal en As MyReport)
Dim warnings As Warning() = Nothing
Dim streamids As String() = Nothing
Dim mimeType As String = Nothing
Dim encoding As String = Nothing
Dim extension As String = Nothing
Dim bytes As Byte()
bytes = aReport.ServerReport.Render("WORD", Nothing, mimeType, encoding, extension, streamids, warnings)
Response.Buffer = True
Response.Clear()
Response.ContentType = mimeType
Response.AddHeader("content-disposition", "attachment; filename=" & en.ToString() & "." + extension)
Response.BinaryWrite(bytes)
Response.Flush()
Response.End()
End Sub
And I'm getting this error :
Server cannot append header after HTTP headers have been sent.
How can I change the code so that I can loop this piece of code? Thanks.
EDIT :
I added this line after Response.End()
Response.Redirect(Request.Url.AbsoluteUri)
And I get this error :
Cannot redirect after HTTP headers have been sent.
WWW works on a request / response mechanism. For every request there is only 1 response. You cannot change that basic mechanism. When browser sends a request it is expecting one and only one response. So if it receives more than 1 response, it either issues a warning to the user to block this behaviour or may choose to ignore the extra responses by itself. Thus these extra responses may be lost.
Having said that you have 2 options with you:
Zip all the files that you want to download and download as a single file.
You can use Popular framework Ionic.Zip.
First, keep all your files in a local directory on the server.
Then use this library to zip the entire folder.
Pseudo code:
Imports (var zip = New Ionic.Zip.ZipFile())
{
zip.AddDirectory("DirectoryOnDisk", "rootInZipFile")
Response.Clear()
Response.AddHeader("Content-Disposition", "attachment; filename=DownloadedFile.zip")
Response.ContentType = "application/zip"
zip.Save(Response.OutputStream)
Response.End()
}
Add a mechanism to issue multiple request using Javascript to get multiple responses, so browser still treats this behaviour as normal.
A normal web page will have a load of (headers) stuff set up for you already, but you don't want any of that: you want complete control over what is sent to the browser. If you cause a redirect to something which sends the headers shown in code later here, the browser will (normally) download the data.
In the code-behind you can have something like
Protected Sub btn_click(ByVal sender As Object, ByVal e As EventArgs) Handles btn.Click
Response.Redirect("~/sendfile.ashx?ref=" & enReference, False)
Context.ApplicationInstance.CompleteRequest()
End Sub
You will also need to add a generic handler (right-click on the project in Solution Explorer, Add->New Item... -> Visual Basic--Web--General choose "Generic Handler"; give it a name like sendfile.ashx) which is somewhat like
Imports System.IO
Public Class sendfile
Implements System.Web.IHttpHandler, IReadOnlySessionState
Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
Dim enReference = context.Request.QueryString("ref")
' do whatever is needed to get the report from enReference '
Dim bytes As Byte() = aReport.ServerReport.Render("WORD", Nothing, mimeType, encoding, extension, streamids, warnings)
Dim downloadName = yourfilename & "." & yourextension
context.Response.ContentType = "application/octet-stream"
context.Response.AddHeader("content-disposition", "attachment; filename=""" & downloadName & """" )
context.Response.BinaryWrite(bytes)
End Sub
ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
Get
Return False
End Get
End Property
End Class
and you will need to work out the code to create the data to be sent.
If you don't need to use session state then you can remove the , IReadOnlySessionState part on the Implements line.
You might need to add context.Response.Flush(). If you find that the response does not have a Content-Length header, then you ought to add one so that the browser can show a meaningful download progress.

download server generated file (.vcf)

i have an interactive aspx dialog with some address data (like name, email, address,...). Now i want the user to be able by clicking a button to download the address data as vcf file.
Now, generating the vcf compatible string isn't the problem. But saving it to the client is.
While it returns the vcf string just fine, it does not open a "Save AS"-dialog. Below i attached my logic for the file download.
What am i doing wrong?
(Maybe it's worth mentioning that the code-behind function calls come from java script,...)
Thanks for any helpfull answers in advance.
Public Sub SaveText(ByVal Text As String)
Dim FileName As String = System.IO.Path.GetRandomFileName()
Using sw As New System.IO.StreamWriter(Server.MapPath(FileName + ".txt"))
sw.WriteLine(Text)
sw.Close()
End Using
Dim fs As System.IO.FileStream = Nothing
fs = System.IO.File.Open(Server.MapPath(FileName + ".txt"), System.IO.FileMode.Open)
Dim btFile(fs.Length) As Byte
fs.Read(btFile, 0, fs.Length)
fs.Close()
With HttpContext.Current.Response
.Clear()
.Buffer = True
.Expires = 0
.AddHeader("Content-disposition", "attachment;filename=" + FileName)
.AddHeader("Content-Length", btFile.Length.ToString)
.ContentType = "application/octet-stream"
.BinaryWrite(btFile)
'.OutputStream.Write(btFile, 0, btFile.Length())
.Flush()
.End()
End With
End Sub
Ok, the problem was not the above mentioned logic itself. The way i handeled the response on the client side was just wrong. The calling java script function expected something else.
I would elaborate in more detail, but this stuff here is so home grown and proprietary, it wouldn't make any sense.
Cheers.

Asp.Net hide url of file

I hav an Asp.NET site that displays a PDF/Word/Excel file but I would like to hide the location of the file i.e. if the user requests a file via a link instead of displaying the path just open the filename, I've seen some other posts on other sites but since they are old they do not wok.
Any help is appreciated.
Thanks
Use ASP.NET ASHX Handler
Some ASP.NET files are dynamically generated. They are generated with C# code or disk resources. These files do not require web forms. Instead, an ASHX generic handler is ideal. It can dynamically return an image from a query string, write XML, or any other data.
One way to get around this is to write a custom .ashx handler, which implements IHttpHandler.
Implement the ProcessRequest(..) method, and pipe the file out in the response (here's an example from an application I wrote a while back:
Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
Dim inline As Boolean = Boolean.Parse(context.Request.QueryString("Inline"))
Dim fileName As String = context.Request.QueryString("fileName")
If (fileName.Contains("\")) Then Throw New Exception(String.Format("Invalid filename {0}. Looks like a path was attempted", fileName))
Dim filePath = ConfigurationManager.AppSettings("FileDirectory") + "\" + fileName
With context.Response
.Buffer = True
.Clear()
If inline Then
.AddHeader("content-disposition", "inline; ; filename=" & IO.Path.GetFileName(filePath))
Else
.AddHeader("content-disposition", "attachment; ; filename=" & IO.Path.GetFileName(filePath))
End If
.WriteFile(filePath)
If fileName.ToUpper.EndsWith(".PDF") Then
.ContentType = "application/pdf"
ElseIf fileName.EndsWith(".htm") Or fileName.EndsWith(".html") Then
.ContentType = "text/html"
ElseIf fileName.EndsWith(".tif") Then
.ContentType = "image/tiff"
ElseIf fileName.EndsWith(".jpeg") Or fileName.EndsWith(".jpg") Then
.ContentType = "image/jpeg"
End If
.End()
End With

Sending CSV created on the fly back to client for download

I'm converting a bunch of FOXPRO / FOXWEB apps to ASP.NET.
The underlying DB is still foxpro (for the moment).
I am passing a table to some VB.NET code that I want to have converted to a CSV file and sent back to the client for download. And it works! Sort of ... It works sometimes, but at other times, instead of asking me if I want to download the CSV file, it just spews the file to the browser window.
On the asp side, I am passing the response object, the table and the csv file name.
<%
Dim xls_fn As String = "test01.csv"
'OLEDB call to fill up 'tbl' ... this works.
sendTableAsCSVtoClient(response, tbl, xls_fn)
%>
In the file clsCommon.vb, I have the following code:
Option Explicit On
'Option Strict On
Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.Page
Imports System.IO
Imports Microsoft.VisualBasic
Imports System.Diagnostics
Imports System.Data
Imports System.Data.OleDb
Public Class clsCommon
Inherits Page
Public Shared Function enq(ByVal str As String) As String
Dim dq As String
dq = """"
Return dq & str & dq
End Function
' some other functions and subs defined in here ... blah blah blah
' ...
Public Shared Function sendTableAsCSVtoClient(ByVal resp As HttpResponse, ByVal sqlTable As DataTable, ByVal xls_fn As String) As Boolean
Dim r As DataRow
Dim c As DataColumn
Dim sep As String = ","
Dim FileExtension As String
Dim lcFileNameONLY As String
Dim i As Integer
Dim dq As String = """"
FileExtension = UCase(Path.GetExtension(xls_fn))
lcFileNameONLY = UCase(Path.GetFileNameWithoutExtension(xls_fn))
resp.Clear()
resp.ClearContent()
resp.ClearHeaders()
resp.ContentType = "application/vnd.ms-excel"
resp.AddHeader("Content-Disposition", "inline; filename=" & lcFileNameONLY & ".csv")
For Each c In sqlTable.Columns
resp.Write(UCase(c.ColumnName) & sep)
Next
resp.Write(vbCrLf)
For Each r In sqlTable.Rows
For i = 0 To sqlTable.Columns.Count - 1
resp.Write(enq(r(i)) & sep)
Next
resp.Write(vbCrLf)
Next
resp.End()
Return True
End Function
End Class
What's causing this?
How do I get around it?
I'm guessing it doesn't really matter that the source of the data is a table.
Note that the file is created on the fly and never exists on the file system of the server.
tx,
tff
Instead of using a Content-Disposition header that is Inline, use Attachment - this will always prompt for a download.
Change the following line from:
resp.AddHeader("Content-Disposition", "inline; filename=" & lcFileNameONLY & ".csv")
To
resp.AddHeader("Content-Disposition", "attachment; filename=" & lcFileNameONLY & ".csv")
See this and this for examples.
The inline type means that the browser is free to render it inline (within the browser), if in knows how to.
And see this SO question, asking why inline sometimes prompts for downloads (the exact opposite of your question...).
The issue is your Content-disposition header. It should be "attachment" instead of "inline".
You may also want to set the content type to be "text/csv"instead of "application/vnd.ms-excel". This way you're more accurate, and if they prefer to use something else for CSV it should work better. However for an in-house app perhaps vnd.ms-excel might work better?
I agree with the Oded and chmullig that you should change the Content-Disposition, but I also recommend using the buffer and finishing the response with a flush:
resp.Clear()
resp.Buffer = true
'build csv
resp.Flush()
resp.Close()
I believe a call to .End() throws a ThreadAbortException to stop execution which can cause issues depending on how you handle your exceptions. See here for more info

IE7 having problems when redirected to large excel files

We've got a page in our asp.net web system that uses response.redirect to redirect the user directly to an excel file so it will download "automatically" without the user having to to a right click / save as.
This works great - except for files over about 100k in IE7. Other browsers just download the large file fine, and IE works fine under that threshold, but at somewhere about 200k, IE just starts handing out "page cannot be displayed" errors.
Obviously, we'd like the user to be able to download in IE as well - any ideas? Is there some kind of download size threshold thing I can override?
You can make a simple ashx wrapper around this file and force IE to download this file by using http header: "Content-disposition: attachment; filename=fname.xls"
How To Raise a "File Download" Dialog Box for a Known MIME Type
I prefer a different method for sending files. It works for me with all kinds of different types and sizes of files.
Instead of using Response.Redirect, allow the link to the file to do a postback where you modify the response, like so:
Public Shared Sub SendFileToBrowser(ByRef response As HttpResponse, ByVal filepath As String, Optional ByVal filename As String = "", Optional ByVal contentType As String = "", Optional ByVal disposition As String = "", Optional ByVal contentLength As String = "")
Dim ext As String = filepath.Substring(filepath.Length - 3, 3)
If String.IsNullOrEmpty(contentType) Then
If ext = "pdf" Then
contentType = "application/pdf"
Else
contentType = "application/file"
End If
End If
If String.IsNullOrEmpty(disposition) Then
disposition = "attachment"
End If
If String.IsNullOrEmpty(filename) Then
''//Test for relative url path
Dim fileparts As String() = filepath.Split("/")
If fileparts.Length > 1 Then
filename = fileparts(fileparts.Length - 1)
Else
''//Test for absolute file path
Dim fileparts2 As String() = filepath.Split("\") ''//" SO: Fix syntax highlighting
If fileparts2.Length > 1 Then
filename = fileparts2(fileparts2.Length - 1)
Else
''//Just give it a temp name
filename = "temp." & ext
End If
End If
End If
response.Clear()
response.AddHeader("content-disposition", disposition & ";filename=" & filename)
If Not String.IsNullOrEmpty(contentLength) Then
response.AddHeader("Content-Length", contentLength)
End If
response.ContentType = contentType
response.Cache.SetCacheability(HttpCacheability.Public)
response.TransmitFile(filepath)
response.End()
End Sub
Note: Using "''//" for comments so that the syntax highlighter works properly. This still compiles properly as well.
This works for us in IE6 and IE7.

Resources