c# - 从控制器操作下载文件损坏
问题描述
我正在生成一些 JSON 内容,然后 GZipping 该内容,然后从 MVC 控制器操作将 gzip 压缩的内容返回给用户。
内容的生成和 gzipping 工作正常,因为我可以将生成的文件输出到磁盘,然后我可以使用 GZip 打开该文件。但是,当内容返回到浏览器时,内容已损坏。
我尝试了几种不同的方法来将内容返回到浏览器,例如
return File(byte[], "application/gzip");
return new FileStreamResult(stream, "application/gzip")
并且还使用 BinaryWrite() 和 WriteFile() 方法直接写入响应
无论我做什么,我在浏览器中收到的文件都是损坏的。
此代码显示了我当前尝试返回文件内容的方式。
// 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 读取,但浏览器接收到的文件已损坏,因此我确信我的文件创建正常。但不知何故,在将文件写入响应之后,它被损坏了。
我确实想知道是否某种 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)写入响应时的内容与 UTF-8 编码相冲突。我无法弄清楚如何,就像上面的示例一样,我正在使用 Encoding.ASCII.GetBytes() 将我的字符串转换为 byte[] 数组。
我试过设置
Response.Charset = Encoding.ASCII.EncodingName;
Response.ContentEncoding = Encoding.ASCII;
但这仍然不会导致下载有效文件。
编辑 3
我已将问题缩小到数据的 GZip 加密。如果我不加密数据,那么纯文本文件可以正常下载。但是,加密 byte[] 数组然后将该 byte[] 数组写入 Repsonse 会导致看起来像 UTF-8 编码的问题。任何值超过 127 的字节都会被我进一步提到的 3 个字节损坏。我无法弄清楚为什么响应以这种方式处理这些加密数据。我的假设是,当 Byte[] 数组只是作为 byte[] 数组的纯文本时,则处理得很好。只要它是一个正确的 byte[] 数组,即不仅仅是一个作为 byte[] 数组的字符串,那么响应中就会进行一些其他的编码转换。
解决方案
您可以尝试ActionFilterAttribute
基本上响应过滤器会在写入时查看响应输出流并转换流经它的数据。
ASP.NET MVC 中的 GZip/Deflate 压缩
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()
{...}
推荐阅读
- outlook - 401 身份验证错误 MS Graph 与 Outlook 365 帐户
- python - 如何找到特定文件的路径
- java - 如何在我的项目中找到寻址/访问文件系统的每一段代码?
- java - Java 等待不释放锁
- json - 如何在 BASH 中将 JSON 文件导入 PostgreSQL 数据库
- c# - 调用编译查询时如何修复“System.InvalidOperationException:读取器关闭时调用读取的尝试无效”
- c# - 使用 Specflow 时覆盖自定义异常的测试执行状态
- android - Android分页库:如果它不在本地数据库中并且不中断分页,如何显示相关项目?
- c++ - 动态声明新结构 - 可能的用例
- sql - SQL:使用动态数据透视查询计算列