30

Im trying to download and save a file from a HttpWebResponse but im having problems saving the file (other than Text Files) properly.

I think its something to do with this part:

byte[] byteArray = Encoding.UTF8.GetBytes(http.Response.Content);
MemoryStream stream = new MemoryStream(byteArray);

Text Files work fine with the above code but when I try to save the Content to an Image file it gets corrupted. How do i write this 'string' data to an image file (and other binary files)

Forgot to mention, This is .NET CP 3.5 and I have a wrapper class around the HttpWebResponse class to add OAuth etc.

Robin Salih
  • 529
  • 1
  • 6
  • 21
dkarzon
  • 7,868
  • 9
  • 48
  • 61
  • 1
    Please post your code that actually writes to the file. Maybe you are writing it in text mode. You should be writing it in binary mode. – Senthil May 29 '10 at 08:07
  • @Senthil: No, it's the reading code which is problematic - and that's already been shown. – Jon Skeet May 29 '10 at 08:09
  • Hmmm.. is the problem the Encoding.UTF8.GetBytes() part? – Senthil May 29 '10 at 08:10
  • 1
    @Senthil: Well partly that - and partly the fact that he's already getting the content as a string, even if it's not. – Jon Skeet May 29 '10 at 08:11

3 Answers3

65

The problem is you're interpreting the binary data as text, even if it isn't - as soon as you start treating the content as a string instead of bytes, you're in trouble. You haven't given the details of your wrapper class, but I'm assuming your Content property is returning a string - you won't be able to use that. If your wrapper class doesn't let you get at the raw data from the web response, you'll need to modify it.

If you're using .NET 4, you can use the new CopyTo method:

using (Stream output = File.OpenWrite("file.dat"))
using (Stream input = http.Response.GetResponseStream())
{
    input.CopyTo(output);
}

If you're not using .NET 4, you have to do the copying manually:

using (Stream output = File.OpenWrite("file.dat"))
using (Stream input = http.Response.GetResponseStream())
{
    byte[] buffer = new byte[8192];
    int bytesRead;
    while ((bytesRead = input.Read(buffer, 0, buffer.Length)) > 0)
    {
        output.Write(buffer, 0, bytesRead);
    }
}
johnny 5
  • 19,893
  • 50
  • 121
  • 195
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Yeh I thought that was the problem. Added more details (.NET Compact 3.5) – dkarzon May 29 '10 at 08:08
  • @d1k_is: The second snippet should work fine in the compact framework (other than possibly changing your output stream). – Jon Skeet May 29 '10 at 08:08
  • Yep that was the problem. So i had to modify the httpresponse wrapper so it read the stream to a file instead of just to the Content string property. Thanks! – dkarzon May 29 '10 at 09:03
  • 1
    I get "the name http does not exist in the current context" (.NET 4.5 C# app) with the first snippet; I reckon I would get it with the second one, too. – B. Clay Shannon-B. Crow Raven Sep 10 '14 at 21:58
  • 1
    @B.ClayShannon: Well you're need an `HttpWebResponse`, as per the question... (That already uses a variable called `http`.) – Jon Skeet Sep 10 '14 at 22:32
  • Actually, I'm trying to do this with an HttpWebRequest object (I want to save the file that has been sent to a REST method on the server). I did get "using (Stream input = await Request.Content.ReadAsStreamAsync())" to compile. – B. Clay Shannon-B. Crow Raven Sep 10 '14 at 22:38
  • 1
    @B.ClayShannon: I'm not sure how - if `Request` is an `HttpWebRequest`, where is it getting the `Content` property from? I can't see such a property... It sounds like possibly you should ask a new question with more details. (Bear in mind that this question is over 4 years old...) – Jon Skeet Sep 10 '14 at 22:43
  • My bad - "Request" is actually an HttpRequestMessage. – B. Clay Shannon-B. Crow Raven Sep 10 '14 at 22:59
8

Use WebClient.DownloadFile. You can do it manually (something like this), but WebClient is the best bet for simple downloads.

Matthew Flaschen
  • 278,309
  • 50
  • 514
  • 539
0

Simplest way I found is:

  • Use ReadAsByteArrayAsync to get the data.
  • List item File.WriteAllBytes to save the file.

No need to loop through anything. Example in context below:

[TestClass]
public class HttpTests
{
    [TestMethod]
    public void TestMethod1()
    {
        var jsObject = (dynamic)new JObject();
        jsObject.html =  "samplehtml.html";
        jsObject.format = "jpeg";
        const string url = "https://mywebservice.com/api";

        var result =   GetResponse(url, jsObject).GetAwaiter().GetResult();
        File.WriteAllBytes($@"c:\temp\httpresult.{jsObject.format}", result);
    }

    static async Task<byte[]> GetResponse(string uri, dynamic jsObj)
    {
        var httpClient = new HttpClient();
        var load = jsObj.ToString();
        var content = new StringContent(load, Encoding.UTF8, "application/json");
        var response = await httpClient.PostAsync(uri, content);
        return await response.Content.ReadAsByteArrayAsync();
    }
}
johnny 5
  • 19,893
  • 50
  • 121
  • 195
I Stand With Russia
  • 6,254
  • 8
  • 39
  • 67
  • 1
    It should be noted that by calling `ReadAsByteArrayAsync`/`WriteAllBytes` the entire file will be loaded into an array before it is written to disk, which will have memory considerations compared to a `Stream`. Also, if `Task` is available then the target framework is .NET 4+, in which case @JonSkeet's answer shows [an available method](https://learn.microsoft.com/dotnet/api/system.io.stream.copyto) that makes it simple to copy from download `Stream` to file `Stream` without any (explicitly written) loops. – Lance U. Matthews May 09 '18 at 22:16
  • There is no way in hell I would challenge the testament of the great JS or one of his disciples :) – I Stand With Russia May 09 '18 at 23:05