Como retornar um arquivo (FileContentResult) no WebAPI do ASP.NET

Em um controlador MVC comum, podemos produzir pdf com um FileContentResult .

 public FileContentResult Test(TestViewModel vm) { var stream = new MemoryStream(); //... add content to the stream. return File(stream.GetBuffer(), "application/pdf", "test.pdf"); } 

Mas como podemos alterá-lo em um ApiController ?

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

Aqui está o que eu tentei, mas parece que não funciona.

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

O resultado retornado exibido no navegador é:

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

E há uma postagem semelhante no SO: Retornando o arquivo binário do controlador na API da Web do ASP.NET . Fala sobre a saída de um arquivo existente. Mas eu não consegui fazer funcionar com um stream.

Alguma sugestão?

Em vez de retornar o StreamContent como Content , posso fazê-lo funcionar com 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; } 

Se você quiser retornar o IHttpActionResult você pode fazer assim:

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

Essa questão me ajudou.

Então, tente isto:

Código do controlador:

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

Ver marcação HTML (com evento click e url simples):

   Data 

Eu não sei exatamente qual parte da culpa, mas aqui está porque o MemoryStream não funciona para você:

Conforme você escreve no MemoryStream , ele incrementa sua propriedade Position . O construtor do StreamContent leva em consideração a Position atual do stream. Portanto, se você gravar no stream, em seguida, passá-lo para StreamContent , a resposta será iniciada a partir do nada no final do stream.

Há duas maneiras de corrigir isso corretamente:

1) construir conteúdo, escreva para 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) escrever para transmitir, redefinir posição, construir conteúdo

 [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) parece um pouco melhor se você tiver um novo Stream, 1) é mais simples se o seu stream não começa em 0

Para mim foi a diferença entre

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

e

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

O primeiro deles estava retornando a representação JSON de StringContent: {“Cabeçalhos”: [{“Key”: “Tipo de Conteúdo”, “Valor”: [“application / octet-stream; charset = utf-8”]}]}

Enquanto o segundo estava retornando o arquivo propriamente dito.

Parece que Request.CreateResponse tem uma sobrecarga que usa uma string como o segundo parâmetro e parece ter sido isso que estava fazendo com que o próprio object StringContent fosse renderizado como uma string, em vez do conteúdo real.