I am generating some JSON content, and then GZipping that content, before returning the gzipped content to the user, from an MVC Controller Action.
The generation of the content, and gzipping, is working correctly as I can output the generated file to disk, and then I can open that file using GZip. However, when the content is returned to the browser, the content has been corrupted.
I have tried several different approaches to returning the content to the browser, such as
return File(byte[], "application/gzip");
return new FileStreamResult(stream, "application/gzip")
And also writing directly to the Response using BinaryWrite() and WriteFile() methods
No matter what I do, the file I receive in the browser is corrupt.
This code shows the manner in which I am currently trying to return the file content.
// 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();
As the file I am creating can be written to disk, and can be read using Gzip, but the file received by the browser is corrupt, I am confident that my file creation is OK. But somehow after writing the file to the Response, it is being corrupted.
I did wonder if maybe some sort of HTTPHandler is manipulating the result, but I haven't added any Handlers (that I can see).
I am running the application locally currently through IISExpress. How can I check what HttpHandlers/HttpModules are being applied to the pipeline?
Ultimately I expect to receive the exact same file in my browser as is written to disk.
For reference, my generated content is 132 bytes in length, but the browser receives 216 bytes. I have noticed when looking at the byte structure of the received data, there is a repeating pattern of 3 bytes in the content, with the values 239, 191, 189. It almost looks like the resultant byte array has been stuffed or padded with these 3 bytes.
EDIT
Here is a standalone Action method which demonstrated the issue.
[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();
}
}
Here I am spoofing my JSON content, and then compressing it. The file written to disk works fine, and can be opened with my GZip application (I use 7-zip). However, the file received by the browser is corrupt. 7-zip cannot recognise it as a gzip file.
EDIT 2
So it looks like (Thanks to #Will) that the content when written to Response is falling foul of UTF-8 encoding. I cannot work out how though, as in my example above I am using Encoding.ASCII.GetBytes() to convert my string to a byte[] array.
I've tried setting the
Response.Charset = Encoding.ASCII.EncodingName;
Response.ContentEncoding = Encoding.ASCII;
But this still doesn't result in a valid file downloaded.
Edit 3
I've narrowed down the issue to the GZip encryption of the data. If I do not encrypt the data, then the plain text file downloads fine. However, encrypting the byte[] array and then writing that byte[] array to the Repsonse is resulting in what seems like UTF-8 encoding issues. Any bytes with a value over 127 are corrupted with the 3 bytes I mention further up. I cannot work out why the Response is treating this encrypted data in this way. My assumption is that when the Byte[] array is just plain text as a byte[] array, then this is handled fine. As soon as it is a proper byte[] array, i.e not just a string as a byte[] array, then some other conversion of encoding is going on in the Response.
You can try ActionFilterAttribute
Basically response filters look at the Response output stream as it's written and convert the data flowing through it.
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()
{...}
Related
I have a very weird behaviour in a legacy repository that I cloned and try to work on.
There is an action for downloading files (files are hosted on AWS):
(redundant code omitted for clarity).
public virtual async Task<ActionResult> Entity(EntityModel entity)
{
using (Stream stream = downloadFileObject(model.Updates[GetUpdateNumberFromQueryString()].UpdateLink))
using (MemoryStream ms = new MemoryStream())
{
await stream.CopyToAsync(ms).ConfigureAwait(false);
return File(ms.ToArray(), "application/octet-stream", "The file.exe");
}
}
The DownloadFileObject method connects to AWS, creates a request and returns the response stream:
public Stream GetS3FileObject(string objectKey, string fileName)
{
GetObjectRequest request = new GetObjectRequest
{
BucketName = bucketName,
Key = objectKey,
};
GetObjectResponse response = s3Client.GetObject(request);
return response.ResponseStream;
}
This returns a proper stream with no errors, I can see that the lenght indicates something around 35 mb, which is the proper size of the file I am getting.
Now, I tried several approaches for returning this stream as downloadable file, and they all result in something very weird.
Instead of getting the file, the browser retrieves a 110kb text file which pretty much looks like my current view html...
The returned HTML contains the following error message (apart from the regular site content)
Error executing child request for handler
'System.Web.Mvc.HttpHandlerUtil+ServerExecuteHttpHandlerAsyncWrapper'.
OutputStream is not available when a custom TextWriter is used.
I tried several approaches, where the first and most obvious was just:
var stream = DownloadFileObject(model.Updates[GetUpdateNumberFromQueryString()].UpdateLink);
return File(stream, "application/octet-stream", "The file.exe");
Then I tried to copy that stream to a byte array and return that - however, in all cases the result is the same.
Up to the last moment, the debugger shows that my stream/byte array is 35mb large.
Here are the headers:
Also, the rendered Download button html is as below:
Download update
And the button is being created in the view without any HTML helpers (Action.Link() etc)
#Model.SiteLabelDownloadUpdate
Any ideas what can be going on?
I'm trying to send a XML file on request, but I'm getting an error when I'm trying to copy the stream, which I'm loading the file into, to the output stream.
Right now it's working fine if I'm making the request (I use HttpListener btw) from a browser; it shows me my .xml just fine. But I'd also like to be able to download the .xml when I make the request.
Any suggestions?
string xString = #"C:\Src\Capabilities.xml";
XDocument capabilities = XDocument.Load(xString);
Stream stream = response.OutputStream;
response.ContentType = "text/xml";
capabilities.Save(stream);
CopyStream(stream, response.OutputStream);
stream.Close();
public static void CopyStream(Stream input, Stream output)
{
input.CopyTo(output);
}
The error I'm getting is at input.CopyTo(output); : "Stream does not support reading."
You probably get the error because the stream input actually is the response.OutputStream, which is an output stream and also makes the source and target of the copy operation the same stream - huh?
Essentially what your code does now (and this is wrong): You save the XML content to the response's output stream (which essentially already sends it to the browser). Then you try to copy the output stream into the output stream. This doesn't work and even if it did - why? You already wrote to the output stream.
You can simplify all this greatly in my opinion as follows:
// Read the XML text into a variable - why use XDocument at all?
string xString = #"C:\Src\Capabilities.xml";
string xmlText = File.ReadAllText(xString);
// Create an UTF8 byte buffer from it (assuming UTF8 is the desired encoding)
byte[] xmlBuffer = Encoding.UTF8.GetBytes(xmlText);
// Write the UTF8 byte buffer to the response stream
Stream stream = response.OutputStream;
response.ContentType = "text/xml";
response.ContentEncoding = Encoding.UTF8;
stream.Write(xmlBuffer, 0, xmlBuffer.Length);
// Done
stream.Close();
I want to send a url as query string e.g.
localhost/abc.aspx?url=http:/ /www.site.com/report.pdf
and detect if the above URL returns the PDF file. If it will return PDF then it gets saved automatically otherwise it gives error.
There are some pages that uses Handler to fetch the files so in that case also I want to detect and download the same.
localhost/abc.aspx?url=http:/ /www.site.com/page.aspx?fileId=223344
The above may return a pdf file.
What is best way to capture this?
Thanks
You can download a PDF like this
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(uri);
HttpWebResponse response = req.GetResponse();
//check the filetype returned
string contentType = response.ContentType;
if(contentType!=null)
{
splitString = contentType.Split(';');
fileType = splitString[0];
}
//see if its PDF
if(fileType!=null && fileType=="application/pdf"){
Stream stream = response.GetResponseStream();
//save it
using(FileStream fileStream = File.Create(fileFullPath)){
// Initialize the bytes array with the stream length and then fill it with data
byte[] bytesInStream = new byte[stream.Length];
stream.Read(bytesInStream, 0, bytesInStream.Length);
// Use write method to write to the file specified above
fileStream.Write(bytesInStream, 0, bytesInStream.Length);
}
}
response.Close();
The fact that it may come from an .aspx handler doesn't actually matter, it's the mime returned in the server response that is used.
If you are getting a generic mime type, like application/octet-stream then you must use a more heuristical approach.
Assuming you cannot simply use the file extension (eg for .aspx), then you can copy the file to a MemoryStream first (see How to get a MemoryStream from a Stream in .NET?). Once you have a memory stream of the file, you can take a 'cheeky' peek at it (I say cheeky because it's not the correct way to parse a PDF file)
I'm not an expert on PDF format, but I believe reading the first 5 chars with an ASCII reader will yield "%PDF-", so you can identify that with
bool isPDF;
using( StreamReader srAsciiFromStream = new StreamReader(memoryStream,
System.Text.Encoding.ASCII)){
isPDF = srAsciiFromStream.ReadLine().StartsWith("%PDF-");
}
//set the memory stream back to the start so you can save the file
memoryStream.Position = 0;
I'm getting a file from a database in byte [] format and want user to see download dialog before Linq will take it from the database. It's in C# and ASP.NET.
Now, it's like this:
User choose a file, click on it.
In code I get id of file clicked and using Linq I'm downloading.
Then I send the file by Response.OutputStream.Write(content, 0,
content.Length);
Before a file is downloaded from the database user won't see any
download dialog.
What can I do if I want users to see the download dialog before file is downloaded?
Code:
Getting file by id:
public static byte[] getFile(Guid id)
{
var linqFile = from file in MyDB.Files
where file.IdPliku.Equals(id)
select new
{
Content = file.Content
};
return linqFile.ToList().FirstOrDefault().Content.ToArray();
}
Saving file:
public void SaveFile(Guid fileID, string filename, string mimeTypes)
{
try
{
byte[] content = FileService.getFile(fileID);
Response.ClearContent();
Response.ClearHeaders();
Response.ContentType = mimeTypes;
Response.AppendHeader("Accept-Ranges", "bytes");
Response.AppendHeader("Content-Range", string.Format("0-{0}/{1}", content.Length, content.Length));
Response.AppendHeader("Content-Length", content.Length.ToString());
Response.AppendHeader("Content-Encoding", "utf-8");
Response.AppendHeader("Content-Type", Response.ContentType);
Response.AppendHeader("Content-Disposition", "attachment; filename= " + HttpUtility.UrlEncode(filename));
Response.OutputStream.Write(content, 0, content.Length);
//Response.BinaryWrite(content);
Response.Flush();
}
finally
{
Response.Close();
}
}
You are my hope.
your issue is here:
byte[] content = FileService.getFile(fileID);
because in this line you allocate the whole file in the web server's RAM and put everything in there, all content of the file from the database; what happens later does not matter anymore because you have already downloaded from db to web server in this line!!!
I am having such Deja-vu because I am sure I have given exactly the same comment on a very same question few weeks ago. Can't find it now, search for something like this here in SO.
In fact the solution is to stream directly to the output stream of the Response avoiding your byte[] array allocation above, to get this your data layer should of course support it and if it does not you could add a method for this. You want to use SQL Server filestream or something similar.
I have raw data of base64Binary.
string base64BinaryStr = "J9JbWFnZ......"
How can I make pdf file? I know it need some conversion. Please help me.
Step 1 is converting from your base64 string to a byte array:
byte[] bytes = Convert.FromBase64String(base64BinaryStr);
Step 2 is saving the byte array to disk:
System.IO.FileStream stream =
new FileStream(#"C:\file.pdf", FileMode.CreateNew);
System.IO.BinaryWriter writer =
new BinaryWriter(stream);
writer.Write(bytes, 0, bytes.Length);
writer.Close();
using (System.IO.FileStream stream = System.IO.File.Create("c:\\temp\\file.pdf"))
{
System.Byte[] byteArray = System.Convert.FromBase64String(base64BinaryStr);
stream.Write(byteArray, 0, byteArray.Length);
}
First convert the Bas64 string to byte[] and write it into a file.
byte[] bytes = Convert.FromBase64String(base64BinaryStr);
File.WriteAllBytes(#"FolderPath\pdfFileName.pdf", bytes );
This code does not write any file on the hard drive.
Response.AddHeader("Content-Type", "application/pdf");
Response.AddHeader("Content-Length", base64Result.Length.ToString());
Response.AddHeader("Content-Disposition", "inline;");
Response.AddHeader("Cache-Control", "private, max-age=0, must-revalidate");
Response.AddHeader("Pragma", "public");
Response.BinaryWrite(Convert.FromBase64String(base64Result));
Note: the variable base64Result contains the Base64-String: "JVBERi0xLjMgCiXi48/TIAoxI..."
All you need to do is run it through any Base64 decoder which will take your data as a string and pass back an array of bytes. Then, simply write that file out with pdf in the file name.
Or, if you are streaming this back to a browser, simple write the bytes to the output stream, marking the appropriate mime-type in the headers.
Most languages either have built in methods for converted to/from Base64. Or a simple Google with your specific language will return numerous implementations you can use. The process of going back and forth to Base64 is pretty straightforward and can be implemented by even novice developers.
base64BinaryStr - from webservice SOAP message
byte[] bytes = Convert.FromBase64String(base64BinaryStr);