¿Por qué los archivos.docx se dañan al descargar desde una página ASP.NET?
httpresponse (8)
Tengo este código siguiente para traer adjuntos de página al usuario:
private void GetFile(string package, string filename)
{
var stream = new MemoryStream();
try
{
using (ZipFile zip = ZipFile.Read(package))
{
zip[filename].Extract(stream);
}
}
catch (System.Exception ex)
{
throw new Exception("Resources_FileNotFound", ex);
}
Response.ClearContent();
Response.ClearHeaders();
Response.ContentType = "application/unknown";
if (filename.EndsWith(".docx"))
{
Response.ContentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
}
Response.AddHeader("Content-Disposition", "attachment;filename=/"" + filename + "/"");
Response.BinaryWrite(stream.GetBuffer());
stream.Dispose();
Response.Flush();
HttpContext.Current.ApplicationInstance.CompleteRequest();
}
El problema es que todos los archivos compatibles funcionan correctamente (jpg, gif, png, pdf, doc, etc.), pero los archivos .docx, cuando se descargan, están dañados y Office debe corregirlos para poder abrirlos.
Al principio no sabía si el problema consistía en descomprimir el archivo zip que contenía el archivo .docx, así que en lugar de poner el archivo de salida solo en la respuesta, lo guardé primero, y el archivo se abrió correctamente, así que sé el problema debe estar en respuesta por escrito.
¿Sabes lo que puede estar pasando?
Cuando almacene un archivo binario en SQL Server, tenga en cuenta que un archivo se rellena con el límite de palabra más cercano, por lo que potencialmente puede agregar un byte adicional a un archivo. La solución es almacenar el tamaño del archivo original en la base de datos cuando almacena el archivo, y usarlo para la longitud que se debe pasar a la función de escritura del objeto Stream. "Stream.Write (bytes (), 0, longitud)". Esta es la ÚNICA manera confiable de obtener el tamaño de archivo correcto, lo cual es muy importante para Office 2007 y los archivos superiores, que no permiten que aparezcan caracteres adicionales al final (a la mayoría de los otros tipos de archivos como jpg no les importa).
Eche un vistazo a esto: Escribir MemoryStream en un objeto de respuesta
Tuve el mismo problema y la única solución que funcionó para mí fue:
Response.Clear();
Response.ContentType = "Application/msword";
Response.AddHeader("Content-Disposition", "attachment; filename=myfile.docx");
Response.BinaryWrite(myMemoryStream.ToArray());
// myMemoryStream.WriteTo(Response.OutputStream); //works too
Response.Flush();
Response.Close();
Response.End();
No debe usar stream.GetBuffer()
porque devuelve la matriz del búfer que puede contener bytes no utilizados. Utilice stream.ToArray()
lugar. Además, ¿has intentado llamar a stream.Seek(0, SeekOrigin.Begin)
antes de escribir algo?
Atentamente,
Oliver Hanappi
Por lo que vale la pena, también me encontré con el mismo problema enumerado aquí. Para mí, el problema era en realidad con el código de carga, no el código de descarga:
Public Sub ImportStream(FileStream As Stream)
''Use this method with FileUpload.PostedFile.InputStream as a parameter, for example.
Dim arrBuffer(FileStream.Length) As Byte
FileStream.Seek(0, SeekOrigin.Begin)
FileStream.Read(arrBuffer, 0, FileStream.Length)
Me.FileImage = arrBuffer
End Sub
En este ejemplo, el problema es que declaro que la matriz de bytes arrBuffer
con un tamaño de un byte es demasiado grande. Este byte nulo se guarda con la imagen del archivo en la base de datos y se reproduce en la descarga. El código corregido sería:
Dim arrBuffer(FileStream.Length - 1) As Byte
También como referencia mi código HttpResponse
es el siguiente:
context.Response.Clear()
context.Response.ClearHeaders()
''SetContentType() is a function which looks up the correct mime type
''and also adds and informational header about the lookup process...
context.Response.ContentType = SetContentType(objPostedFile.FileName, context.Response)
context.Response.AddHeader("content-disposition", "attachment;filename=" & HttpUtility.UrlPathEncode(objPostedFile.FileName))
''For reference: Public Property FileImage As Byte()
context.Response.BinaryWrite(objPostedFile.FileImage)
context.Response.Flush()
Si utiliza el enfoque anterior que utiliza response.Close (), los gestores de descargas como IE10 dirán "no se puede descargar el archivo" porque las longitudes de los bytes no coinciden con los encabezados. Consulte la documentación. NO use respuesta. Cierre. SIEMPRE. Sin embargo, usar solo el verbo CompeteRequest no desactiva la escritura de bytes en el flujo de salida, por lo que las aplicaciones basadas en XML como WORD 2007 verán la docx como dañada. En este caso, rompa la regla a NUNCA use Response.End. El siguiente código resuelve ambos problemas. Sus resultados pueden variar.
''*** transfer package file memory buffer to output stream
Response.ClearContent()
Response.ClearHeaders()
Response.AddHeader("content-disposition", "attachment; filename=" + NewDocFileName)
Me.Response.ContentType = "application/vnd.ms-word.document.12"
Response.ContentEncoding = System.Text.Encoding.UTF8
strDocument.Position = 0
strDocument.WriteTo(Response.OutputStream)
strDocument.Close()
Response.Flush()
''See documentation at http://blogs.msdn.com/b/aspnetue/archive/2010/05/25/response-end-response-close-and-how-customer-feedback-helps-us-improve-msdn-documentation.aspx
HttpContext.Current.ApplicationInstance.CompleteRequest() ''This is the preferred method
''Response.Close() ''BAD pattern. Do not use this approach, will cause ''cannot download file'' in IE10 and other download managers that compare content-Header to actual byte count
Response.End() ''BAD Pattern as well. However, CompleteRequest does not terminate sending bytes, so Word or other XML based appns will see the file as corrupted. So use this to solve it.
@Cesar: estás utilizando response.Close -> ¿puedes intentarlo con IE 10? Apuesto a que no funciona (los conteos de bytes no coinciden)
También encontré este problema y encontré la respuesta aquí: http://www.aspmessageboard.com/showthread.php?t=230778
Resulta que el formato docx debe tener Response.End () justo después de Response.BinaryWrite.
Todo se ve bien. Mi única idea es intentar llamar a Dispose en su transmisión después de llamar a Response.Flush en lugar de antes, en caso de que los bytes no se escriban por completo antes de vaciar.
Tuve el mismo problema al intentar abrir documentos .docx y .xlsx. Resuelvo el problema definiendo el almacenamiento en caché en ServerAndPrivate en lugar de NoCache
hay mi método para llamar a documento:
public void ProcessRequest(HttpContext context)
{
var fi = new FileInfo(context.Request.Path);
var mediaId = ResolveMediaIdFromName(fi.Name);
if (mediaId == null) return;
int mediaContentId;
if (!int.TryParse(mediaId, out mediaContentId)) return;
var media = _repository.GetPublicationMediaById(mediaContentId);
if (media == null) return;
var fileNameFull = string.Format("{0}{1}", media.Name, media.Extension);
context.Response.Clear();
context.Response.AddHeader("content-disposition", string.Format("attachment;filename={0}", fileNameFull));
context.Response.Charset = "";
context.Response.Cache.SetCacheability(HttpCacheability.ServerAndPrivate);
context.Response.ContentType = media.ContentType;
context.Response.BinaryWrite(media.Content);
context.Response.Flush();
context.Response.End();
}