.NET 4.6.1中修改docx内存返回下载的问题及报错排查求助
问题:docx模板修改后内存生成并返回下载,以及接口重复调用/连接重置问题
我在Web服务器上存储有docx文件,希望打开模板修改内容后,不将修改保存到磁盘,直接在内存中生成修改后的文件通过Web API返回供下载。
我编写了两个方法:PrintForm2为测试方法,直接下载磁盘文件运行正常;但PrintForm方法会自动被调用两次,Chrome控制台显示ERR_CONNECTION_RESET错误,无法定位原因。
现求助两个问题:
- 如何实现docx模板修改后不存盘直接返回下载?
- 如何解决
PrintForm方法的重复调用及连接重置错误?
环境:非.NET Core,为.NET 4.6.1。
附上原代码:
private HttpResponseMessage PrintForm2() { string templateFolderPath = HttpContext.Current.Server.MapPath("~/Templates"); string filePath = System.IO.Path.Combine(templateFolderPath, "Daily.docx"); HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK); byte[] fileBtyes = File.ReadAllBytes(filePath); response.Content = new StreamContent(new MemoryStream(fileBtyes)); response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/vnd.openxmlformats-officedocument.wordprocessingml.document"); response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment") { FileName = "Daily.docx" }; return response; } private HttpResponseMessage PrintForm(string docName, string bondId, bool useHisHerShe) { var templatesPath = HttpContext.Current.Server.MapPath("~/Templates"); var filePath = System.IO.Path.Combine(templatesPath, docName); using (MemoryStream memStream = new MemoryStream()) { // 使用内存流创建Word处理对象 using (WordprocessingDocument bondDoc = WordprocessingDocument.Open(filePath, true)) { // 获取主文档部分并替换需要修改的文本 var mainPart = bondDoc.MainDocumentPart; string docText; using (StreamReader sr = new StreamReader(mainPart.GetStream())) { docText = sr.ReadToEnd(); } string pattern = @"\{BondType\}"; string replacement = "Testing"; string updatedDocText = Regex.Replace(docText, pattern, replacement); // 写入更新后的内容 using (StreamWriter sw = new StreamWriter(mainPart.GetStream(FileMode.Create, FileAccess.Write))) { sw.Write(updatedDocText); } //bondDoc.Clone(System.IO.Path.Combine(templatesPath, "00-Daily.docx")); } // 将修改后的docx内容复制到内存流 memStream.Seek(0, SeekOrigin.Begin); using (FileStream fs = new FileStream(filePath, FileMode.Open)) { fs.CopyTo(memStream); } var response = new HttpResponseMessage(HttpStatusCode.OK); response.Content = new StreamContent(memStream); response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment") { FileName = docName }; response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/vnd.openxmlformats-officedocument.wordprocessingml.document"); return response; } }
问题1:实现docx模板修改后不存盘直接返回下载
原代码的核心错误是直接修改磁盘上的模板文件,且最终仍读取磁盘文件返回,完全违背“不存盘”需求。正确方案是全程基于内存流操作:
- 先将模板文件读入内存流
- 在内存流上打开
WordprocessingDocument进行修改 - 修改完成后直接用该内存流构建响应
修改后的代码示例:
private HttpResponseMessage PrintForm(string docName, string bondId, bool useHisHerShe) { var templatesPath = HttpContext.Current.Server.MapPath("~/Templates"); var filePath = System.IO.Path.Combine(templatesPath, docName); // 读取模板文件到内存流 byte[] templateBytes = File.ReadAllBytes(filePath); MemoryStream memStream = new MemoryStream(templateBytes); try { // 在内存流上打开文档,操作完全不影响磁盘文件 using (WordprocessingDocument bondDoc = WordprocessingDocument.Open(memStream, true)) { var mainPart = bondDoc.MainDocumentPart; // 读取文档内容 string docText; using (StreamReader sr = new StreamReader(mainPart.GetStream())) { docText = sr.ReadToEnd(); } // 替换目标文本 string pattern = @"\{BondType\}"; string replacement = "Testing"; string updatedDocText = Regex.Replace(docText, pattern, replacement); // 写入修改后的内容 using (StreamWriter sw = new StreamWriter(mainPart.GetStream(FileMode.Create, FileAccess.Write))) { sw.Write(updatedDocText); } // 显式保存修改,避免未提交的内容丢失 bondDoc.Save(); } // 将内存流指针重置到起始位置,确保响应能读取完整内容 memStream.Seek(0, SeekOrigin.Begin); // 构建下载响应 var response = new HttpResponseMessage(HttpStatusCode.OK); response.Content = new StreamContent(memStream); response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment") { FileName = docName }; response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/vnd.openxmlformats-officedocument.wordprocessingml.document"); // 让HttpResponseMessage接管内存流的生命周期,避免提前释放 response.RegisterForDispose(memStream); return response; } catch { // 异常时手动释放内存流 memStream.Dispose(); throw; } }
关键要点:
- 所有操作基于内存流,完全不修改磁盘模板文件
- 修改完成后必须重置内存流指针到起始位置
- 通过
RegisterForDispose让Web API负责释放内存流,避免返回后流被提前销毁
问题2:解决重复调用及连接重置错误
原代码出现问题的核心原因:
- 磁盘文件锁定:原代码直接修改磁盘模板文件,导致文件被锁定,后续读取操作触发异常,引发连接重置
- 内存流生命周期错误:原代码中内存流被
using块包裹,返回时已被释放,Web API读取已释放的流导致错误 - 重复调用:通常是浏览器对失败请求的自动重试,或前端代码重复触发请求
解决步骤:
- 采用问题1中的内存流方案,彻底避免磁盘文件操作,消除文件锁定问题
- 调整内存流的生命周期管理,用
RegisterForDispose替代using块,确保Web API能正常读取流内容 - 检查前端代码:确认按钮点击事件是否绑定多次、AJAX请求是否重复发送,排除前端触发的重复调用
完成以上调整后,连接重置错误会消失,浏览器也不会因请求失败而重试,重复调用问题自然解决。
内容的提问来源于stack exchange,提问作者user441521
相关产品推荐
相关产品推荐

