Blaise
Blaise

Reputation: 22212

How to return a file (FileContentResult) in ASP.NET WebAPI

In a regular MVC controller, we can output pdf with a FileContentResult.

public FileContentResult Test(TestViewModel vm)
{
    var stream = new MemoryStream();
    //... add content to the stream.

    return File(stream.GetBuffer(), "application/pdf", "test.pdf");
}

But how can we change it into an ApiController?

[HttpPost]
public IHttpActionResult Test(TestViewModel vm)
{
     //...
     return Ok(pdfOutput);
}

Here is what I've tried but it doesn't seem to work.

[HttpGet]
public IHttpActionResult Test()
{
    var stream = new MemoryStream();
    //...
    var content = new StreamContent(stream);
    content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
    content.Headers.ContentLength = stream.GetBuffer().Length;
    return Ok(content);            
}

The returned result displayed in the browser is:

{"Headers":[{"Key":"Content-Type","Value":["application/pdf"]},{"Key":"Content-Length","Value":["152844"]}]}

And there is a similar post on SO: Returning binary file from controller in ASP.NET Web API . It talks about output an existing file. But I could not make it work with a stream.

Any suggestions?

Upvotes: 247

Views: 463830

Answers (8)

Jon
Jon

Reputation: 1180

I found this article useful: https://codeburst.io/download-files-using-web-api-ae1d1025f0a9

Basically it says:

[Route("api/[controller]")]
[ApiController]
public class JobController : ControllerBase
{
    [HttpGet]
    public ActionResult GetFile()
    {
        byte[] fileContent = GetFile();
        return File(fileContent, "application/pdf", "test.pdf");
    }
}

Upvotes: 7

Ameer Mohideen
Ameer Mohideen

Reputation: 7

 var memoryStream = new MemoryStream();
                await cloudFile.DownloadToStreamAsync(memoryStream);
                responseMessage.result = "Success";

                var contentType = "application/octet-stream";
            
                **using (var stream = new MemoryStream())
                {                    
                    return File(memoryStream.GetBuffer(), contentType, "Cartage.pdf");
                }**

Upvotes: -3

M.Stramm
M.Stramm

Reputation: 1309

I am not exactly sure which part to blame, but here's why MemoryStream doesn't work for you:

As you write to MemoryStream, it increments its Position property. The constructor of StreamContent takes into account the stream's current Position. So if you write to the stream, then pass it to StreamContent, the response will start from the nothingness at the end of the stream.

There's two ways to properly fix this:

  1. construct content, write to stream

     [HttpGet]
     public HttpResponseMessage Test()
     {
         var stream = new MemoryStream();
         var response = Request.CreateResponse(HttpStatusCode.OK);
         response.Content = new StreamContent(stream);
         // ...
         // stream.Write(...);
         // ...
         return response;
     }
    
  2. write to stream, reset position, construct content

     [HttpGet]
     public HttpResponseMessage Test()
     {
         var stream = new MemoryStream();
         // ...
         // stream.Write(...);
         // ...
         stream.Position = 0;
    
         var response = Request.CreateResponse(HttpStatusCode.OK);
         response.Content = new StreamContent(stream);
         return response;
     }
    
  3. looks a little better if you have a fresh Stream, 1) is simpler if your stream does not start at 0

Upvotes: 8

Simon Mourier
Simon Mourier

Reputation: 138915

Here is an implementation that streams the file's content out without buffering it (buffering in byte[] / MemoryStream, etc. can be a server problem if it's a big file).

public class FileResult : IHttpActionResult
{
    public FileResult(string filePath)
    {
        if (filePath == null)
            throw new ArgumentNullException(nameof(filePath));

        FilePath = filePath;
    }

    public string FilePath { get; }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        var response = new HttpResponseMessage(HttpStatusCode.OK);
        response.Content = new StreamContent(File.OpenRead(FilePath));
        var contentType = MimeMapping.GetMimeMapping(Path.GetExtension(FilePath));
        response.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
        return Task.FromResult(response);
    }
}

It can be simply used like this:

public class MyController : ApiController
{
    public IHttpActionResult Get()
    {
        string filePath = GetSomeValidFilePath();
        return new FileResult(filePath);
    }
}

Upvotes: 26

aleha_84
aleha_84

Reputation: 8539

This question helped me.

So, try this:

Controller code:

[HttpGet]
public HttpResponseMessage Test()
{
    var path = System.Web.HttpContext.Current.Server.MapPath("~/Content/test.docx");;
    HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
    var stream = new FileStream(path, FileMode.Open);
    result.Content = new StreamContent(stream);
    result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
    result.Content.Headers.ContentDisposition.FileName = Path.GetFileName(path);
    result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
    result.Content.Headers.ContentLength = stream.Length;
    return result;          
}

View Html markup (with click event and simple url):

<script type="text/javascript">
    $(document).ready(function () {
        $("#btn").click(function () {
            // httproute = "" - using this to construct proper web api links.
            window.location.href = "@Url.Action("GetFile", "Data", new { httproute = "" })";
        });
    });
</script>


<button id="btn">
    Button text
</button>

<a href=" @Url.Action("GetFile", "Data", new { httproute = "" }) ">Data</a>

Upvotes: 54

EnderWiggin
EnderWiggin

Reputation: 515

For me it was the difference between

var response = Request.CreateResponse(HttpStatusCode.OK, new StringContent(log, System.Text.Encoding.UTF8, "application/octet-stream");

and

var response = Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent(log, System.Text.Encoding.UTF8, "application/octet-stream");

The first one was returning the JSON representation of StringContent: {"Headers":[{"Key":"Content-Type","Value":["application/octet-stream; charset=utf-8"]}]}

While the second one was returning the file proper.

It seems that Request.CreateResponse has an overload that takes a string as the second parameter and this seems to have been what was causing the StringContent object itself to be rendered as a string, instead of the actual content.

Upvotes: 6

Blaise
Blaise

Reputation: 22212

Instead of returning StreamContent as the Content, I can make it work with ByteArrayContent.

[HttpGet]
public HttpResponseMessage Generate()
{
    var stream = new MemoryStream();
    // processing the stream.

    var result = new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = new ByteArrayContent(stream.ToArray())
    };
    result.Content.Headers.ContentDisposition =
        new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
    {
        FileName = "CertificationCard.pdf"
    };
    result.Content.Headers.ContentType =
        new MediaTypeHeaderValue("application/octet-stream");

    return result;
}

Upvotes: 252

Ogglas
Ogglas

Reputation: 69968

If you want to return IHttpActionResult you can do it like this:

[HttpGet]
public IHttpActionResult Test()
{
    var stream = new MemoryStream();

    var result = new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = new ByteArrayContent(stream.GetBuffer())
    };
    result.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
    {
        FileName = "test.pdf"
    };
    result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");

    var response = ResponseMessage(result);

    return response;
}

Upvotes: 120

Related Questions