How to return a file (FileContentResult) i

2019-01-01 15:12发布

问题:

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?

回答1:

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


回答2:

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


回答3:

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>


回答4:

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 it\'s 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;
}

2) looks a little better if you have a fresh Stream, 1) is simpler if your stream does not start at 0



回答5:

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.



回答6:

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