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

C#asp.netasp.net Mvcasp.net Web-Api

C# Problem Overview


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?

C# Solutions


Solution 1 - C#

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;
}

Solution 2 - C#

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;
}

Solution 3 - C#

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>

Solution 4 - C#

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);
    }
}

Solution 5 - C#

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

Solution 6 - C#

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.

Solution 7 - C#

 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");
                }**

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBlaiseView Question on Stackoverflow
Solution 1 - C#BlaiseView Answer on Stackoverflow
Solution 2 - C#OgglasView Answer on Stackoverflow
Solution 3 - C#alehaView Answer on Stackoverflow
Solution 4 - C#Simon MourierView Answer on Stackoverflow
Solution 5 - C#M.StrammView Answer on Stackoverflow
Solution 6 - C#EnderWigginView Answer on Stackoverflow
Solution 7 - C#Ameer MohideenView Answer on Stackoverflow