从Controller Action下载的文件已损坏

时间:2019-05-16 16:14:16

标签: c# asp.net-mvc

我正在生成一些JSON内容,然后从MVC控制器操作将压缩后的内容返回给用户之前,对该内容进行GZipping。

由于可以将生成的文件输出到磁盘,然后可以使用GZip打开该文件,因此内容的生成和gzipping工作正常。但是,将内容返回到浏览器时,内容已损坏。

我尝试了几种将内容返回到浏览器的方法,例如

return File(byte[], "application/gzip");

return new FileStreamResult(stream, "application/gzip")

还可以使用BinaryWrite()和WriteFile()方法直接写入Response

无论我做什么,在浏览器中收到的文件都已损坏。

此代码显示了我当前尝试返回文件内容的方式。

// This line writes my content byte[] array to disk.  This file when opened with gzip works fine.
System.IO.File.WriteAllBytes(@"C:\temp\test.vcp", result.FileBytes);


// Writing out the byte array to the Response results in a corrupt file.  I have also attempted to Response.WriteFile(@"C:\temp\test.vcp") which also results in a corrupt file.

Response.Clear();
Response.ContentType = "application/gzip";

Response.AppendHeader("Content-Disposition", cd.ToString());
Response.AddHeader("Content-Length", result.FileBytes.Length.ToString());

Response.BinaryWrite(result.FileBytes);
Response.Flush();
Response.Close();
Response.End();

由于我正在创建的文件可以写入磁盘,并且可以使用Gzip读取,但是浏览器收到的文件已损坏,因此我确信我的文件创建正常。但是以某种方式将文件写入Response后,它已损坏。

我确实想知道是否某种HTTPHandler正在操纵结果,但是我没有添加任何处理程序(我可以看到)。

我当前正在通过IISExpress在本地运行该应用程序。如何检查正在将哪些HttpHandlers / HttpModules应用于管道?

我最终希望在浏览器中收到与写入磁盘完全相同的文件。

作为参考,我生成的内容的长度为132个字节,但浏览器接收到216个字节。我注意到在查看接收到的数据的字节结构时,内容中存在3个字节的重复模式,其值分别为239、191、189。看起来好像结果字节数组已被这些填充或填充3个字节。

编辑

这是演示问题的独立Action方法。

    [HttpGet]
    public void GetFile()
    {

        byte[] text = Encoding.ASCII.GetBytes(@"{""PetName"":""Doggy McDocFace"",""OwnerName"":""Kurt""}");
        byte[] compressed = Compress(text);

        var cd = new System.Net.Mime.ContentDisposition
        {
            // for example foo.bak
            FileName = "ExampleFile.vcp",

            // always prompt the user for downloading, set to true if you want 
            // the browser to try to show the file inline
            Inline = true,
        };


        System.IO.File.WriteAllBytes(@"C:\temp\ExampleFile.vcp", compressed);


        Response.Clear();
        Response.ContentType = "application/gzip";

        Response.AppendHeader("Content-Disposition", cd.ToString());
        Response.AddHeader("Content-Length", compressed.Length.ToString());

        Response.BinaryWrite(compressed);
        Response.Flush();
        Response.Close();
        Response.End();
    }

    public byte[] Compress(byte[] raw)
    {
        using (var memory = new MemoryStream())
        {
            using (var gzip = new GZipStream(memory, CompressionMode.Compress, true))
            {
                gzip.Write(raw, 0, raw.Length);
            }
            return memory.ToArray();
        }
    }

在这里,我欺骗了我的JSON内容,然后对其进行压缩。写入磁盘的文件可以正常工作,并且可以使用我的GZip应用程序打开(我使用7-zip)。但是,浏览器收到的文件已损坏。 7-zip无法将其识别为gzip文件。

编辑2

因此,(感谢@Will)看起来,写到Response时的内容会受到UTF-8编码的污染。我无法确定该如何做,就像上面的示例一样,我正在使用Encoding.ASCII.GetBytes()将我的字符串转换为byte []数组。

我尝试设置

       Response.Charset = Encoding.ASCII.EncodingName;
       Response.ContentEncoding = Encoding.ASCII;

但这仍然不会导致下载有效文件。

编辑3

我已将问题缩小到数据的GZip加密。如果我不加密数据,那么纯文本文件可以很好地下载。但是,加密byte []数组,然后将该byte []数组写入Repsonse会导致看起来像UTF-8编码的问题。值超过127的任何字节都会损坏,我将进一步提到3个字节。我无法弄清楚为什么Response以这种方式处理此加密数据。我的假设是,当Byte []数组只是纯文本作为byte []数组时,则可以很好地处理。一旦它是一个适当的byte []数组,即不仅仅是一个字符串作为byte []数组,那么响应中还将进行其他一些编码转换。

1 个答案:

答案 0 :(得分:0)

您可以尝试ActionFilterAttribute 基本上,响应过滤器会在编写响应输出流时对其进行查看,并转换流经它的数据。

GZip/Deflate Compression in ASP.NET MVC

public class CompressAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {

        var encodingsAccepted = filterContext.HttpContext.Request.Headers["Accept-Encoding"];
        if (string.IsNullOrEmpty(encodingsAccepted)) return;

        encodingsAccepted = encodingsAccepted.ToLowerInvariant();
        var response = filterContext.HttpContext.Response;

        if (encodingsAccepted.Contains("deflate"))
        {
            response.AppendHeader("Content-encoding", "deflate");
            response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress);
        }
        else if (encodingsAccepted.Contains("gzip"))
        {
            response.AppendHeader("Content-encoding", "gzip");
            response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
        }
    }
}
[Compress]
[HttpGet]
public ActionResult GetFile()
{...}