By Josh Earl


2012-03-02 22:37:15 8 Comments

I'm working on a web service using ASP.NET MVC's new WebAPI that will serve up binary files, mostly .cab and .exe files.

The following controller method seems to work, meaning that it returns a file, but it's setting the content type to application/json:

public HttpResponseMessage<Stream> Post(string version, string environment, string filetype)
{
    var path = @"C:\Temp\test.exe";
    var stream = new FileStream(path, FileMode.Open);
    return new HttpResponseMessage<Stream>(stream, new MediaTypeHeaderValue("application/octet-stream"));
}

Is there a better way to do this?

7 comments

@KMJungersen 2019-01-21 22:45:05

For those using .NET Core:

You can make use of the IActionResult interface in an API controller method, like so...

    [HttpGet("GetReportData/{year}")]
    public async Task<IActionResult> GetReportData(int year)
    {
        // Render Excel document in memory and return as Byte[]
        Byte[] file = await this._reportDao.RenderReportAsExcel(year);

        return File(file, "application/vnd.openxmlformats", "fileName.xlsx");
    }

This example is simplified, but should get the point across. In .NET Core this process is so much simpler than in previous versions of .NET - i.e. no setting response type, content, headers, etc.

Also, of course the MIME type for the file and the extension will depend on individual needs.

Reference: SO Post Answer by @NKosi

@Ronnie Overby 2014-01-02 17:52:08

For Web API 2, you can implement IHttpActionResult. Here's mine:

using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;

class FileResult : IHttpActionResult
{
    private readonly string _filePath;
    private readonly string _contentType;

    public FileResult(string filePath, string contentType = null)
    {
        if (filePath == null) throw new ArgumentNullException("filePath");

        _filePath = filePath;
        _contentType = contentType;
    }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        var response = new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StreamContent(File.OpenRead(_filePath))
        };

        var contentType = _contentType ?? MimeMapping.GetMimeMapping(Path.GetExtension(_filePath));
        response.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);

        return Task.FromResult(response);
    }
}

Then something like this in your controller:

[Route("Images/{*imagePath}")]
public IHttpActionResult GetImage(string imagePath)
{
    var serverPath = Path.Combine(_rootPath, imagePath);
    var fileInfo = new FileInfo(serverPath);

    return !fileInfo.Exists
        ? (IHttpActionResult) NotFound()
        : new FileResult(fileInfo.FullName);
}

And here's one way you can tell IIS to ignore requests with an extension so that the request will make it to the controller:

<!-- web.config -->
<system.webServer>
  <modules runAllManagedModulesForAllRequests="true"/>

@GowthamanSS 2014-01-13 10:28:33

will the above code works for downloading zip file

@Ronnie Overby 2014-01-13 15:28:05

No sorry it doesn't work for zip files.

@B. Clay Shannon 2014-02-19 00:24:52

@RonnieOverby: What does this mean: "tell IIS to ignore requests with an extension so that the request will make it to the controller" What sort of request has an extension, and how would having an extension potentially cause it to not make it to the Controller?

@Ronnie Overby 2014-02-19 01:31:45

It means that the web server must be instructed to treat requests that look like static asset requests differently than it normally does.

@Krzysztof Morcinek 2014-11-20 12:33:05

Nice answer, not always SO code runs just after pasting and for different cases (different files).

@Jony Adamit 2015-03-24 13:47:42

According to Stephen Cleary and answers here in SO like in this example you should avoid using Task.Run in Asp.Net applications. Try using Task.FromResult instead.

@Ronnie Overby 2015-03-24 20:49:26

@JonyAdamit Thanks. I think another option is to place an async modifier on the method signature and remove the creation of a task altogether: gist.github.com/ronnieoverby/ae0982c7832c531a9022

@Jony Adamit 2015-03-25 08:39:19

I think both methods are similar. They both run synchronously. Except the method you suggested generates a warning while the other doesn't. In any way, thanks for the code snippet - already adapted it in my project :)

@Index 2015-10-09 12:41:29

Just a heads up for anyone coming over this running IIS7+. runAllManagedModulesForAllRequests can now be omitted.

@Cory Nelson 2015-11-23 17:15:43

Note, you should gracefully handle FileNotFoundException from File.Open. It is a race condition to merely check if a file exists before opening the file.

@adam0101 2016-07-14 21:25:58

Couldn't someone just pass in a path like "../../../../allMyPasswords.txt" and get to a file they shouldn't using this?!?!

@Ronnie Overby 2016-07-15 11:46:06

@adam0101 That's what ACL's are for.

@Julian Corrêa 2016-07-29 14:51:48

Excellent approach.

@BendEg 2016-08-19 08:17:41

@RonnieOverby does the api take care of closing the file stream?

@Ronnie Overby 2016-08-19 15:29:37

@BendEg Seems like at one time I checked the source and it did. And it makes sense that it should. Not being able to control the source of the framework, any answer to this question could change over time.

@BrainSlugs83 2017-04-12 19:30:22

There's actually already a built in FileResult (and even FileStreamResult) class.

@Ronnie Overby 2018-10-22 14:32:28

@BrainSlugs83 I think those built in types are for MVC controllers (derive from ActionResult). At the time I wrote this, we were talking about Web API 2, which was a different framework from MVC 5. Unless someone has come up with a way to convert an ActionResult to an IHttpActionResult, I don't think you can use the built in FileResult implementations.

@Mitesh Prajapati 2018-11-30 07:26:15

will this work for 4 GB file?

@Ronnie Overby 2018-11-30 16:44:02

Sorry the limit is 3.99999999999999 GB

@carlosfigueira 2012-03-03 21:09:45

Try using a simple HttpResponseMessage with its Content property set to a StreamContent:

// using System.IO;
// using System.Net.Http;
// using System.Net.Http.Headers;

public HttpResponseMessage Post(string version, string environment,
    string filetype)
{
    var path = @"C:\Temp\test.exe";
    HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
    var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
    result.Content = new StreamContent(stream);
    result.Content.Headers.ContentType = 
        new MediaTypeHeaderValue("application/octet-stream");
    return result;
}

A few things to note about the stream used:

  • You must not call stream.Dispose(), since Web API still needs to be able to access it when it processes the controller method's result to send data back to the client. Therefore, do not use a using (var stream = …) block. Web API will dispose the stream for you.

  • Make sure that the stream has its current position set to 0 (i.e. the beginning of the stream's data). In the above example, this is a given since you've only just opened the file. However, in other scenarios (such as when you first write some binary data to a MemoryStream), make sure to stream.Seek(0, SeekOrigin.Begin); or set stream.Position = 0;

  • With file streams, explicitly specifying FileAccess.Read permission can help prevent access rights issues on web servers; IIS application pool accounts are often given only read / list / execute access rights to the wwwroot.

@Steve Guidi 2012-04-20 18:36:59

Would you happen to know when the stream gets closed? I am assuming the framework ultimately calls HttpResponseMessage.Dispose(), which in turn calls HttpResponseMessage.Content.Dispose() effectively closing the stream.

@Dan Gartner 2012-08-22 20:02:44

Steve - you're correct and I verified by adding a breakpoint to FileStream.Dispose and running this code. The framework calls HttpResponseMessage.Dispose, which calls StreamContent.Dispose, which calls FileStream.Dispose.

@carlosfigueira 2013-06-19 16:18:59

You can't really add a using to either the result (HttpResponseMessage) or the stream itself, since they'll still be used outside the method. As @Dan mentioned, they're disposed by the framework after it's done sending the response to the client.

@GowthamanSS 2014-01-13 10:27:26

@Drew Noakes will the above code works for downloading zip file

@Drew Noakes 2014-01-13 10:48:57

@GowthamanSS, sure. You may like to use the MIME type for ZIP files though.

@Drew Noakes 2014-01-13 14:14:05

@GowthamanSS, I suggest you ask a new question rather than seek help in the comments on an existing question!

@GowthamanSS 2014-01-13 14:50:45

@Gabriel S. 2014-02-07 08:31:48

Besides setting the async option in the FileStream constructor, is this approach as efficient as it could be in terms of asynchronous reading from the file stream? Does the Web API infrastructure do it properly?

@B. Clay Shannon 2014-02-19 00:29:59

How does the other end of this work? How does the client receive the returned result? Is it simply a matter of assigning what's returned to a filestream and then saving that file to disk?

@carlosfigueira 2014-02-20 14:53:28

@B.ClayShannon yes, that's about it. As far as the client is concerned it's just a bunch of bytes in the content of the HTTP response. The client can do with those bytes whatever they choose, including saving it to a local file.

@Zach 2014-12-12 09:06:50

@carlosfigueira, hi, do you know how to delete the file after the bytes are all sent?

@carlosfigueira 2014-12-12 19:26:15

You can implement your own Stream class that wraps the file being streamed, and when it's done you can close the file stream and then delete the file.

@Peter Morris 2015-08-12 10:38:32

Try also adding the line -> result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = "test.exe" };

@Bart Verkoeijen 2017-06-14 09:49:01

To avoid content negotiation this method doesn't use Request.CreateResponse(): stackoverflow.com/questions/22060357

@joym8 2019-04-26 18:56:10

If something breaks during processing, what should be returned from Post method?

@JBA 2014-11-28 04:41:24

For anyone having the problem of the API being called more than once while downloading a fairly large file using the method in the accepted answer, please set response buffering to true System.Web.HttpContext.Current.Response.Buffer = true;

This makes sure that the entire binary content is buffered on the server side before it is sent to the client. Otherwise you will see multiple request being sent to the controller and if you do not handle it properly, the file will become corrupt.

@decates 2017-10-06 07:42:14

The Buffer property has been deprecated in favour of BufferOutput. It defaults to true.

@Eric Boumendil 2014-07-29 16:03:15

While the suggested solution works fine, there is another way to return a byte array from the controller, with response stream properly formatted :

  • In the request, set header "Accept: application/octet-stream".
  • Server-side, add a media type formatter to support this mime type.

Unfortunately, WebApi does not include any formatter for "application/octet-stream". There is an implementation here on GitHub: BinaryMediaTypeFormatter (there are minor adaptations to make it works for webapi 2, method signatures changed).

You can add this formatter into your global config :

HttpConfiguration config;
// ...
config.Formatters.Add(new BinaryMediaTypeFormatter(false));

WebApi should now use BinaryMediaTypeFormatter if the request specifies the correct Accept header.

I prefer this solution because an action controller returning byte[] is more comfortable to test. Though, the other solution allows you more control if you want to return another content-type than "application/octet-stream" (for example "image/gif").

@MickySmig 2012-08-01 15:23:01

You could try

httpResponseMessage.Content.Headers.Add("Content-Type", "application/octet-stream");

@David Peden 2012-03-02 22:58:02

The overload that you're using sets the enumeration of serialization formatters. You need to specify the content type explicitly like:

httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");

@Josh Earl 2012-03-03 14:34:29

Thanks for the reply. I tried this out, and I'm still seeing Content Type: application/json in Fiddler. The Content Type appears to be set correctly if I break before returning the httpResponseMessage response. Any more ideas?

Related Questions

Sponsored Content

16 Answered Questions

[SOLVED] Can an ASP.NET MVC controller return an Image?

21 Answered Questions

[SOLVED] File Upload ASP.NET MVC 3.0

6 Answered Questions

[SOLVED] How to secure an ASP.NET Web API

9 Answered Questions

36 Answered Questions

[SOLVED] How do you create a dropdownlist from an enum in ASP.NET MVC?

25 Answered Questions

[SOLVED] ASP.NET Web Site or ASP.NET Web Application?

29 Answered Questions

[SOLVED] How do I get ASP.NET Web API to return JSON instead of XML using Chrome?

11 Answered Questions

[SOLVED] Best practice to return errors in ASP.NET Web API

11 Answered Questions

[SOLVED] WCF vs ASP.NET Web API

1 Answered Questions

Sponsored Content