17

I'm using the below sample code for writing and downloading a memory stream to a file in C#.

MemoryStream memoryStream = new MemoryStream();
TextWriter textWriter = new StreamWriter(memoryStream);
textWriter.WriteLine("Something");           
byte[] bytesInStream = new byte[memoryStream.Length];
memoryStream.Write(bytesInStream, 0, bytesInStream.Length);
memoryStream.Close();          
Response.Clear();
Response.ContentType = "application/force-download";
Response.AddHeader("content-disposition",
                   "attachment; filename=name_you_file.xls");
Response.BinaryWrite(bytesInStream);
Response.End();

I am getting the following error:

Specified argument was out of the range of valid values.
Parameter name: offset

What may be the cause?

John Willemse
  • 6,608
  • 7
  • 31
  • 45
user1357872
  • 783
  • 3
  • 11
  • 31
  • What does the debugger tell you? Is the size of `bytesInStream` greater than 0? – Tieson T. May 15 '13 at 06:37
  • @Kiarash - no, third parameter is the number of elements to write (ref http://msdn.microsoft.com/en-us/library/system.io.memorystream.write.aspx) – Peter Lillevold May 15 '13 at 06:37
  • where do you get the error I mean which line? – Kiarash May 15 '13 at 06:38
  • In the line Response.BinaryWrite(bytesInStream); – user1357872 May 15 '13 at 06:39
  • 3
    I'm not sure what you're trying to do here, but have you missed that `MemoryStream` has a [`ToArray`](http://msdn.microsoft.com/en-us/library/system.io.memorystream.toarray.aspx) method that gives you a `byte[]` equivalent? – Damien_The_Unbeliever May 15 '13 at 06:40
  • you have missed textWriter.Flush(); after textWriter.WriteLine("Something"); otherwise you memorystream would be empty and you will get the error – Kiarash May 15 '13 at 06:52

4 Answers4

33

At the point in your code where you copy the data to an array, the TextWriter might not have flushed the data. This will happen when you Flush() or when you Close() it.

See if this works:

MemoryStream memoryStream = new MemoryStream();
TextWriter textWriter = new StreamWriter(memoryStream);
textWriter.WriteLine("Something");   
textWriter.Flush(); // added this line
byte[] bytesInStream = memoryStream.ToArray(); // simpler way of converting to array
memoryStream.Close(); 

Response.Clear();
Response.ContentType = "application/force-download";
Response.AddHeader("content-disposition", "attachment;    filename=name_you_file.xls");
Response.BinaryWrite(bytesInStream);
Response.End();
krembanan
  • 1,408
  • 12
  • 28
  • As I recall, that works fine on Windows 7, but fails on XP (Windows Server 2003). Besides, you don't put ms.position = 0. – Stefan Steiger May 15 '13 at 07:01
  • 1
    @Quandary - if you read up on the documentation for `ToArray` you will see that positioning the stream is not necessary. I quote: "Writes the stream contents to a byte array, regardless of the Position property." – Peter Lillevold May 15 '13 at 07:03
  • @Peter Lillevold: Make that: should write the Byte Array regardless of Position, and even does that on Windows 7. – Stefan Steiger May 15 '13 at 07:05
  • Thanks to both @PeterLillevold and (at)Quandary for very good inputs, I didn't know there where OS differences here. Any suggestions for changes in the code above to make it safer? – krembanan May 15 '13 at 07:59
6

You are doing something wrong logically here. First, you write some text to the MemoryStream and then you write an empty array to the same stream. I assume you are trying to copy the contents of the stream into the bytesInStream array. You can create this array by calling memoryStream.ToArray().

Alternatively, you can avoid the array copying by writing the stream directly to the response output stream using MemoryStream.CopyTo. Replace your BinaryWrite call with this:

 memoryStream.Position = 0;
 memoryStream.CopyTo(Response.OutputStream);

Note: explicitly position the stream at the start since CopyTo will copy from the current position.

Peter Lillevold
  • 33,668
  • 7
  • 97
  • 131
  • Can you provide an example? – user1357872 May 15 '13 at 06:43
  • You'll need to seek the memory stream to the beginning using `.Seek` for `CopyTo` to actually copy anything. – Joshua May 15 '13 at 06:56
  • No example needed, just put ms.position = 0 in there. – Stefan Steiger May 15 '13 at 06:56
  • @Quandary - if you read up on the documentation for `ToArray` you will see that positioning the stream is not necessary. I quote: "Writes the stream contents to a byte array, regardless of the Position property." Also, an example is good here, since I propose a different approach than copying to an intermediate array. – Peter Lillevold May 15 '13 at 07:04
3

OK, since one obviously gets downvoted for providing just a working example, let me Elaborate:

First, you don't do

textWriter.Flush()

and expect the Content of textwriter to have been flushed to memorystream.

Then you don't do

memoryStream.Position = 0

And expect the memorystream to be "written" from Position 0.

Then you do

memoryStream.Write(bytesInStream, 0, bytesInStream.Length);

but what you actually mean is

memoryStream.Read(bytesInStream, 0, CInt(memoryStream.Length))

You also missed that length is Long, while read uses an integer, so you can get an exception there.

So this is your code minimally adapted to "work" (i copied it into a vb Project)

Imports System.Web
Imports System.Web.Services

Public Class TextHandler
    Implements System.Web.IHttpHandler

    Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest

        'context.Response.ContentType = "text/plain"
        'context.Response.Write("Hello World!")


        Dim memoryStream As New System.IO.MemoryStream()
        Dim textWriter As System.IO.TextWriter = New System.IO.StreamWriter(memoryStream)
        textWriter.WriteLine("Something")
        textWriter.Flush()

        memoryStream.Position = 0
        Dim bytesInStream As Byte() = New Byte(memoryStream.Length - 1) {}
        'memoryStream.Write(bytesInStream, 0, bytesInStream.Length)
        memoryStream.Read(bytesInStream, 0, CInt(memoryStream.Length))

        memoryStream.Close()
        context.Response.Clear()
        context.Response.ContentType = "application/force-download"
        context.Response.AddHeader("content-disposition", "attachment; filename=name_you_file.txt")
        context.Response.BinaryWrite(bytesInStream)
        context.Response.End()
    End Sub

    ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
        Get
            Return False
        End Get
    End Property

End Class

Then you use

Content-Type: application/force-download 

which means

"I, the web server, am going to lie to you (the browser) about what this file is so that you will not treat it as a PDF/Word Document/MP3/whatever and prompt the user to save the mysterious file to disk instead". It is a dirty hack that breaks horribly when the client doesn't do "save to disk".

...

And finally, you don't encode the filename correctly, so if one uses non-ASCII characters for the filename, it will garble the filename, which is very funny if you happen to be Chinese or Russian and operate entirely outside the ASCII character set.


Original

Here a quick excerpt from one of my ajax handlers. It's VB.NET, when converting, take care on the length -1 things.

Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
    Dim strFileName As String = "Umzugsmitteilung.doc"
    Dim strUID As String = context.Request.QueryString("ump_uid")
    context.Response.Clear()


    'If String.IsNullOrEmpty(strUID) Or fileData Is Nothing Then
    '    context.Response.Write("<script type=""text/javascript"">alert('File does not exist !')</script>")
    '    context.Response.End()
    'End If

    context.Response.ClearContent()

    'context.Response.AddHeader("Content-Disposition", "attachment; filename=" + strFileName)
    context.Response.AddHeader("Content-Disposition", GetContentDisposition(strFileName))

    'context.Response.ContentType = "application/msword"
    context.Response.ContentType = "application/octet-stream"

    GetUmzugsMitteilung(strUID)

    context.Response.End()
End Sub ' ProcessRequest



Public Shared Sub SaveWordDocumentToOutputStream(strUID As String, doc As Aspose.Words.Document)

    Using ms As System.IO.MemoryStream = New System.IO.MemoryStream()
        CreateWordDocumentFromTemplate(strUID, doc, ms)
        ms.Position = 0

        Dim bytes As Byte() = New Byte(ms.Length - 1) {}
        ms.Read(bytes, 0, CInt(ms.Length))

        System.Web.HttpContext.Current.Response.OutputStream.Write(bytes, 0, ms.Length)
        ms.Close()
    End Using ' ms

End Sub ' SaveWordDocumentToOutputStream





    Public Shared Function StripInvalidPathChars(str As String) As String
        If str Is Nothing Then
            Return Nothing
        End If

        Dim strReturnValue As String = ""

        Dim strInvalidPathChars As New String(System.IO.Path.GetInvalidPathChars())

        Dim bIsValid As Boolean = True
        For Each cThisChar As Char In str
            bIsValid = True

            For Each cInvalid As Char In strInvalidPathChars
                If cThisChar = cInvalid Then
                    bIsValid = False
                    Exit For
                End If
            Next cInvalid

            If bIsValid Then
                strReturnValue += cThisChar
            End If
        Next cThisChar

        Return strReturnValue
    End Function ' StripInvalidPathChars


    Public Shared Function GetContentDisposition(ByVal strFileName As String) As String
        ' http://stackoverflow.com/questions/93551/how-to-encode-the-filename-parameter-of-content-disposition-header-in-http
        Dim contentDisposition As String
        strFileName = StripInvalidPathChars(strFileName)

        If System.Web.HttpContext.Current IsNot Nothing AndAlso System.Web.HttpContext.Current.Request.Browser IsNot Nothing Then
            If (System.Web.HttpContext.Current.Request.Browser.Browser = "IE" And (System.Web.HttpContext.Current.Request.Browser.Version = "7.0" Or System.Web.HttpContext.Current.Request.Browser.Version = "8.0")) Then
                contentDisposition = "attachment; filename=" + Uri.EscapeDataString(strFileName).Replace("'", Uri.HexEscape("'"c))
            ElseIf (System.Web.HttpContext.Current.Request.Browser.Browser = "Safari") Then
                contentDisposition = "attachment; filename=" + strFileName
            Else
                contentDisposition = "attachment; filename*=UTF-8''" + Uri.EscapeDataString(strFileName)
            End If
        Else
            contentDisposition = "attachment; filename*=UTF-8''" + Uri.EscapeDataString(strFileName)
        End If

        Return contentDisposition
    End Function ' GetContentDisposition
Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442
0

You're using a very long way to convert string to bytes.
Are you sure, that you need any streams? Why just don't use encoding?

Response.BinaryWrite(Encoding.UTF8.GetBytes("Something"))

Dennis
  • 37,026
  • 10
  • 82
  • 150