133

I am using ASP.NET Web API.
I want to download a PDF with C# from the API (that the API generates).

Can I just have the API return a byte[]? and for the C# application can I just do:

byte[] pdf = client.DownloadData("urlToAPI");? 

and

File.WriteAllBytes()?
Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120
Kyle
  • 32,731
  • 39
  • 134
  • 184
  • "the Web API"? What exactly do you mean? Please read http://tinyurl.com/so-hints and edit your question. – Jon Skeet Jun 20 '12 at 18:09
  • 2
    @JonSkeet: The Web API is a new feature in the latest version of ASP.NET. See http://www.asp.net/whitepapers/mvc4-release-notes#_Toc317096197 – Robert Harvey Jun 20 '12 at 18:11
  • 1
    @Robert: Thanks - the tag makes it clearer, although referring to "the ASP.NET Web API" would have been clearer still. Partly MS's fault for a rubbishly generic name too :) – Jon Skeet Jun 20 '12 at 18:18
  • http://blogs.msdn.com/b/codefx/archive/2012/02/23/more-about-rest-file-upload-download-service-with-asp-net-web-api-and-windows-phone-background-file-transfer.aspx – Robert Harvey Jun 20 '12 at 18:20
  • Anyone who lands wanting to return the stream via web api and IHTTPActionResult then see here: http://nodogmablog.bryanhogan.net/2017/02/downloading-an-inmemory-file-using-web-api-2/ – IbrarMumtaz Nov 07 '17 at 13:00

7 Answers7

199

Better to return HttpResponseMessage with StreamContent inside of it.

Here is example:

public HttpResponseMessage GetFile(string id)
{
    if (String.IsNullOrEmpty(id))
        return Request.CreateResponse(HttpStatusCode.BadRequest);

    string fileName;
    string localFilePath;
    int fileSize;

    localFilePath = getFileFromID(id, out fileName, out fileSize);
       
    HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
    response.Content = new StreamContent(new FileStream(localFilePath, FileMode.Open, FileAccess.Read));
    response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
    response.Content.Headers.ContentDisposition.FileName = fileName;
    response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");

    return response;
}

UPDATE from comment by patridge: Should anyone else get here looking to send out a response from a byte array instead of an actual file, you're going to want to use new ByteArrayContent(someData) instead of StreamContent (see here).

ZAD-Man
  • 1,328
  • 23
  • 42
Regfor
  • 8,515
  • 1
  • 38
  • 51
  • 3
    First thing - this code will cause an exception since you're newing up two FileStream objects pointed at the same file. Second thing is that you do not want to use a "Using" statement, because as soon as the variable goes out of scope, .NET will dispose it and you'll get error messages about the underlying connection being closed. – Brandon Montgomery Sep 26 '12 at 21:44
  • 62
    Should anyone else get here looking to send out a response from a byte array instead of an actual file, you're going to want to use `new ByteArrayContent(someData)` instead of `StreamContent` (see [here](http://stackoverflow.com/a/12468615/48700)). – patridge Jan 25 '13 at 21:53
  • 1
    You may also want to override the base dispose() so you can handle your resources correctly when the framework calls it. – Phil Cooper Jan 16 '14 at 11:29
  • @patridge: Is `ByteArrayContent` better than `response.BinaryWrite`? – Isaac Kleinman Jun 18 '14 at 15:56
  • @IsaacKleinman I'm not sure. I haven't used `Response.BinaryWrite`. I'm guessing it would be throwing bytes at the stream directly rather than managing content using the Web API pipeline you have in place. If you have other processing steps in that API pipeline that need to run on the content before it heads down the wire, you may want to stick with `ByteArrayContent`. Best I can offer is to suggest you see what works best for your situation. – patridge Jun 18 '14 at 21:01
  • What is the `fileSize` variable used for in the above code? Should/could this be sent in the response headers? – hofnarwillie Mar 01 '16 at 12:08
  • 1
    This was a very helpful answer, but a user reported not being able to open pdf files returned to the client in this manner using Firefox. The files would open in Firefox as "filename.pdf.htm", which would cause the file not to open properly. Chrome, IE, Edge all worked fine. I found the solution was that you should also set the ContentType like this: response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf"); – mathias999us Jun 15 '16 at 16:38
  • @mathias999us Updated code accordingly to your comment – Regfor Jun 16 '16 at 13:27
  • 4
    I would like to point out that the correct MediaTypeHeaderValue is crucial and to get it dynamic if you have different file types you can do like this. (where fileName is a string and has a file type ending like .jpg, .pdf, docx etc..) var contentType = MimeMapping.GetMimeMapping(fileName); response.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType); – JimiSweden Jul 19 '16 at 11:16
  • 1
    Does the FileStream get disposed automatically? – Brian Tacker Sep 18 '19 at 11:17
44

I made the follow action:

[HttpGet]
[Route("api/DownloadPdfFile/{id}")]
public HttpResponseMessage DownloadPdfFile(long id)
{
    HttpResponseMessage result = null;
    try
    {
        SQL.File file = db.Files.Where(b => b.ID == id).SingleOrDefault();

        if (file == null)
        {
            result = Request.CreateResponse(HttpStatusCode.Gone);
        }
        else
        {
            // sendo file to client
            byte[] bytes = Convert.FromBase64String(file.pdfBase64);


            result = Request.CreateResponse(HttpStatusCode.OK);
            result.Content = new ByteArrayContent(bytes);
            result.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
            result.Content.Headers.ContentDisposition.FileName = file.name + ".pdf";
        }

        return result;
    }
    catch (Exception ex)
    {
        return Request.CreateResponse(HttpStatusCode.Gone);
    }
}
Hakan Fıstık
  • 16,800
  • 14
  • 110
  • 131
42

Just a note for .Net Core: We can use the FileContentResult and set the contentType to application/octet-stream if we want to send the raw bytes. Example:

[HttpGet("{id}")]
public IActionResult GetDocumentBytes(int id)
{
    byte[] byteArray = GetDocumentByteArray(id);
    return new FileContentResult(byteArray, "application/octet-stream");
}
Amir Shirazi
  • 706
  • 8
  • 9
22

Example with IHttpActionResult in ApiController.

[HttpGet]
[Route("file/{id}/")]
public IHttpActionResult GetFileForCustomer(int id)
{
    if (id == 0)
      return BadRequest();

    var file = GetFile(id);

    IHttpActionResult response;
    HttpResponseMessage responseMsg = new HttpResponseMessage(HttpStatusCode.OK);
    responseMsg.Content = new ByteArrayContent(file.SomeData);
    responseMsg.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
    responseMsg.Content.Headers.ContentDisposition.FileName = file.FileName;
    responseMsg.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
    response = ResponseMessage(responseMsg);
    return response;
}

If you don't want to download the PDF and use a browsers built in PDF viewer instead remove the following two lines:

responseMsg.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
responseMsg.Content.Headers.ContentDisposition.FileName = file.FileName;
Ogglas
  • 62,132
  • 37
  • 328
  • 418
  • @ElbertJohnFelipe Yes, you get the file with `var file = GetFile(id);`. `file.SomeData` is a Byte Array (`byte[]`) and `file.FileName`is `string`. – Ogglas Feb 12 '18 at 12:20
  • Thank you for your post. 'HttpResponseMessage' didn't work for me inside an ApiController, so you saved me. – Max Aug 02 '18 at 21:38
3

You Can try , HttpClient for Download file from another side and same time you can pass as File Result

 [HttpGet]
    [Route("api/getFile")]
    public async  Task<FileResult> GetFile(string Param1,string Param2)
    {
        try
        {
            Stream stream = null;
            string strURL = @"File URL";
            HttpClient client = new HttpClient();
            HttpResponseMessage httpResponse = await client.GetAsync(strURL);
            Stream streamToReadFrom = await httpResponse.Content.ReadAsStreamAsync();
            return File(streamToReadFrom, "{MIME TYPE}");

        }
        catch (Exception ex)
        {

            throw ex;
        }
        finally
        { 
        
        }
    }
2

I've been wondering if there was a simple way to download a file in a more ... "generic" way. I came up with this.

It's a simple ActionResult that will allow you to download a file from a controller call that returns an IHttpActionResult. The file is stored in the byte[] Content. You can turn it into a stream if needs be.

I used this to return files stored in a database's varbinary column.

    public class FileHttpActionResult : IHttpActionResult
    {
        public HttpRequestMessage Request { get; set; }

        public string FileName { get; set; }
        public string MediaType { get; set; }
        public HttpStatusCode StatusCode { get; set; }

        public byte[] Content { get; set; }

        public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
        {
            HttpResponseMessage response = new HttpResponseMessage(StatusCode);

            response.StatusCode = StatusCode;
            response.Content = new StreamContent(new MemoryStream(Content));
            response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
            response.Content.Headers.ContentDisposition.FileName = FileName;
            response.Content.Headers.ContentType = new MediaTypeHeaderValue(MediaType);

            return Task.FromResult(response);
        }
    }
Jake
  • 21
  • 2
1

Another way to download file is to write the stream content to the response's body directly:

[HttpGet("pdfstream/{id}")]
public async Task  GetFile(long id)
{        
    var stream = GetStream(id);
    Response.StatusCode = (int)HttpStatusCode.OK;
    Response.Headers.Add( HeaderNames.ContentDisposition, $"attachment; filename=\"{Guid.NewGuid()}.pdf\"" );
    Response.Headers.Add( HeaderNames.ContentType, "application/pdf"  );            
    await stream.CopyToAsync(Response.Body);
    await Response.Body.FlushAsync();           
}
Adam Moszczyński
  • 3,477
  • 1
  • 17
  • 18