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?
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 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; }
Method 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; }
Method 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(" rel="nofollow noreferrer noopener"GetFile", "Data", new { httproute = "" }) ">Data</a>
Method 4
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); } }
Method 5
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:
-
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; }
-
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; }
- looks a little better if you have a fresh Stream, 1) is simpler if your stream does not start at 0
Method 6
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.
Method 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"); }**
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0