如何将MemoryStream下载为文件?

17

我正在使用以下示例代码在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();

我收到了以下错误信息:

指定的参数超出了有效值范围。
参数名称:偏移量

可能是什么原因引起的?


调试器告诉你什么?bytesInStream的大小是否大于0? - Tieson T.
@Kiarash - 不是,第三个参数是要写入的元素数量(参考 http://msdn.microsoft.com/en-us/library/system.io.memorystream.write.aspx) - Peter Lillevold
你在哪里出错了,我的意思是哪一行? - Kiarash
在这行代码 Response.BinaryWrite(bytesInStream); 中。 - user1357872
3
我不确定你在这里尝试做什么,但你是否忽略了MemoryStream有一个ToArray方法,可以给你一个等效的byte[]数组? - Damien_The_Unbeliever
你在textWriter.WriteLine("Something")之后漏掉了textWriter.Flush();,否则你的memoryStream将是空的,你会得到错误。 - Kiarash
4个回答

34

当你将数据复制到数组时,在代码中的某个点,TextWriter 可能尚未刷新数据。这会在你调用 Flush() 或 Close() 函数时发生。

看看这是否起作用:

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();

据我回忆,这在Windows 7上运行良好,但在XP(Windows Server 2003)上失败。此外,您不需要将ms.position = 0。 - Stefan Steiger
1
@Quandary - 如果你阅读了ToArray的文档,你会发现定位流并不是必要的。我引用一下文档中的话:“将流内容写入字节数组中,而不管Position属性。” - Peter Lillevold
@Peter Lillevold:应该无论位置如何都要写入字节数组,即使在Windows 7上也是如此。 - Stefan Steiger
感谢@PeterLillevold和@Quandary的非常好的建议,我不知道这里有操作系统的差异。对于上面的代码,有什么改动建议可以使其更安全吗? - krembanan

6
您在逻辑上有些错误。首先,您向MemoryStream写入一些文本,然后再向同一流中写入一个空数组。我猜您是想将流的内容复制到bytesInStream数组中。您可以通过调用memoryStream.ToArray()来创建此数组。
或者,您可以通过使用MemoryStream.CopyTo将流直接写入响应输出流来避免数组复制。请将BinaryWrite调用替换为以下内容:
 memoryStream.Position = 0;
 memoryStream.CopyTo(Response.OutputStream);

注意:由于CopyTo将从当前位置开始复制,因此请显式将流定位到开头位置。

你能提供一个例子吗? - user1357872
你需要使用 .Seek 将内存流定位到开头,以便 CopyTo 能够实际复制内容。 - Joshua
请将ms.position = 0放入其中,无需示例。 - Stefan Steiger
@Quandary - 如果您阅读了“ToArray”的文档,您会发现定位流是不必要的。我引用:“无论Position属性如何,将流内容写入字节数组。”此外,提供一个例子也很好,因为我提出了一种与复制到中间数组不同的方法。 - Peter Lillevold

3
好的,由于显然仅提供一个可工作的示例会被投下反对票,所以让我详细解释一下:
首先,你不需要做

textWriter.Flush()

希望您能够将TextWriter的内容刷新到MemoryStream中。

然后,您不需要执行以下操作:

memoryStream.Position = 0

期望将MemoryStream从位置0开始“写入”。

然后执行

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

但实际上你的意思是什么。
memoryStream.Read(bytesInStream, 0, CInt(memoryStream.Length))

您还忽略了长度为Long,而read使用整数,因此您可能会遇到异常。
这是您的代码最小化适应“工作”的方式(我将其复制到了一个vb项目中)。
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

然后您使用:
Content-Type: application/force-download 

这意味着,“我这个Web服务器会向您(浏览器)撒谎,告诉您该文件是什么,以便您不会将其视为PDF / Word文档/ MP3 /任何其他类型的文件,并提示用户将神秘文件保存到磁盘。这是一种肮脏的技巧,当客户端不执行“保存到磁盘”操作时,它会出现严重故障。”

...

最后,您未正确编码文件名,因此如果文件名中使用非ASCII字符,则文件名将变得混乱,如果您恰好是中国人或俄罗斯人,并完全在ASCII字符集之外运行,则非常有趣。


原始内容:

这是我一个ajax处理程序的快速摘录。 它是VB.NET,转换时要注意长度-1的事情。

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
        ' https://dev59.com/SXVD5IYBdhLWcg3wGHeu
        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

4
所以OP需要从你的代码中提取相关部分?我认为你应该这样做,并解释其中的差异和原因。 - CodeCaster
@CodeCaster:简单来说,他忘记了ms.Position = 0,而且他没有正确地编码文件名。 - Stefan Steiger

0
你正在使用一种非常冗长的方式将字符串转换为字节。
你确定需要使用流吗?为什么不直接使用编码呢?

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接