可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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);
}
}