Como retornar um arquivo usando a API Web?

98

Estou usando o ASP.NET Web API .
Quero baixar um PDF com C # da API (que a API gera).

Posso apenas fazer com que a API retorne um byte[]? e para o aplicativo C #, posso apenas fazer:

byte[] pdf = client.DownloadData("urlToAPI");? 

e

File.WriteAllBytes()?
Kyle
fonte
"a API Web"? O que exatamente você quer dizer? Leia tinyurl.com/so-hints e edite sua pergunta.
Jon Skeet de
1
@JonSkeet: A API da Web é um novo recurso na versão mais recente do ASP.NET. Consulte asp.net/whitepapers/mvc4-release-notes#_Toc317096197
Robert Harvey
1
@Robert: Obrigado - a tag torna isso mais claro, embora se referir à "API da Web ASP.NET" fosse ainda mais claro. Em parte, culpa da MS por um nome genérico horrível também :)
Jon Skeet
Quem aterrissar querendo devolver o stream via web api e IHTTPActionResult veja aqui: nodogmablog.bryanhogan.net/2017/02/…
IbrarMumtaz 07/11/17

Respostas:

170

Melhor retornar HttpResponseMessage com StreamContent dentro dele.

Aqui está um exemplo:

public HttpResponseMessage GetFile(string id)
{
    if (String.IsNullOrEmpty(id))
        return Request.CreateResponse(HttpStatusCode.BadRequest);

    string fileName;
    string localFilePath;
    int fileSize;

    localFilePath = getFileFromID(id, out fileName, out fileSize);

    HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
    response.Content = new StreamContent(new FileStream(localFilePath, FileMode.Open, FileAccess.Read));
    response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
    response.Content.Headers.ContentDisposition.FileName = fileName;
    response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");

    return response;
}

UPD do comentário de patridge : Se alguém mais chegar aqui procurando enviar uma resposta de uma matriz de bytes em vez de um arquivo real, você vai querer usar novo ByteArrayContent (someData) em vez de StreamContent (veja aqui ).

Regfor
fonte
1
Primeira coisa - este código causará uma exceção, já que você está criando dois objetos FileStream apontados para o mesmo arquivo. A segunda coisa é que você não deseja usar uma instrução "Usando", porque assim que a variável sair do escopo, o .NET a descartará e você receberá mensagens de erro sobre o fechamento da conexão subjacente.
Brandon Montgomery
48
Se alguém vier aqui para enviar uma resposta de um array de bytes em vez de um arquivo real, você vai querer usar em new ByteArrayContent(someData)vez de StreamContent(veja aqui ).
patridge
Você também pode querer substituir o dispose () de base para que possa manipular seus recursos corretamente quando a estrutura o chamar.
Phil Cooper
1
Gostaria de salientar que o MediaTypeHeaderValue correto é crucial e para torná-lo dinâmico, se você tiver diferentes tipos de arquivo, pode fazer assim. (onde fileName é uma string e tem um tipo de arquivo terminando como .jpg, .pdf, docx etc.) var contentType = MimeMapping.GetMimeMapping (fileName); response.Content.Headers.ContentType = new MediaTypeHeaderValue (contentType);
JimiSweden
1
O FileStream é descartado automaticamente?
Brian Tacker de
37

Eu fiz a seguinte ação:

[HttpGet]
[Route("api/DownloadPdfFile/{id}")]
public HttpResponseMessage DownloadPdfFile(long id)
{
    HttpResponseMessage result = null;
    try
    {
        SQL.File file = db.Files.Where(b => b.ID == id).SingleOrDefault();

        if (file == null)
        {
            result = Request.CreateResponse(HttpStatusCode.Gone);
        }
        else
        {
            // sendo file to client
            byte[] bytes = Convert.FromBase64String(file.pdfBase64);


            result = Request.CreateResponse(HttpStatusCode.OK);
            result.Content = new ByteArrayContent(bytes);
            result.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
            result.Content.Headers.ContentDisposition.FileName = file.name + ".pdf";
        }

        return result;
    }
    catch (Exception ex)
    {
        return Request.CreateResponse(HttpStatusCode.Gone);
    }
}
André de Mattos Ferraz
fonte
Na verdade, isso responde à pergunta
Mick,
1
Isso não seria uma boa ideia com arquivos grandes, pois carrega a imagem inteira na memória. A opção de stream é melhor.
Paul Reedy
@PaulReedy Perfect ... mas em muitos casos, você não precisa lidar com arquivos grandes. Mas concordo totalmente com o seu ponto!
André de Mattos Ferraz
14

Exemplo com IHttpActionResultem ApiController.

[HttpGet]
[Route("file/{id}/")]
public IHttpActionResult GetFileForCustomer(int id)
{
    if (id == 0)
      return BadRequest();

    var file = GetFile(id);

    IHttpActionResult response;
    HttpResponseMessage responseMsg = new HttpResponseMessage(HttpStatusCode.OK);
    responseMsg.Content = new ByteArrayContent(file.SomeData);
    responseMsg.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
    responseMsg.Content.Headers.ContentDisposition.FileName = file.FileName;
    responseMsg.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
    response = ResponseMessage(responseMsg);
    return response;
}

Se você não quiser fazer o download do PDF e usar um navegador integrado ao visualizador de PDF, remova as duas linhas a seguir:

responseMsg.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
responseMsg.Content.Headers.ContentDisposition.FileName = file.FileName;
Ogglas
fonte
@ElbertJohnFelipe Sim, você obtém o arquivo com var file = GetFile(id);. file.SomeDataé um Byte Array ( byte[]) e file.FileNameé string.
Ogglas de
Obrigado pelo seu post. 'HttpResponseMessage' não funcionou para mim dentro de um ApiController, então você me salvou.
Máx.
13

Apenas uma observação .Net Core: podemos usar o FileContentResulte definir o contentType como application/octet-streamse quisermos enviar os bytes brutos. Exemplo:

[HttpGet("{id}")]
public IActionResult GetDocumentBytes(int id)
{
    byte[] byteArray = GetDocumentByteArray(id);
    return new FileContentResult(byteArray, "application/octet-stream");
}
Amir Shirazi
fonte
1
Isso funciona muito bem. Além disso, se você quiser controlar o nome do arquivo, há uma propriedade FileContentResultchamada FileDownloadNamepara especificar o nome do arquivo
semanasdev
@weeksdev ah não sabia disso. Obrigado pelo comentário.
Amir Shirazi
É isso, obrigado. O comentário do weeksdev também é muito útil.
fragg
1

Tenho me perguntado se existe uma maneira simples de baixar um arquivo de uma forma mais ... "genérica". Eu vim com isso.

É um processo simples ActionResultque permite baixar um arquivo de uma chamada de controlador que retorna um IHttpActionResult. O arquivo é armazenado no byte[] Content. Você pode transformá-lo em um fluxo, se necessário.

Usei isso para retornar arquivos armazenados na coluna varbinary de um banco de dados.

    public class FileHttpActionResult : IHttpActionResult
    {
        public HttpRequestMessage Request { get; set; }

        public string FileName { get; set; }
        public string MediaType { get; set; }
        public HttpStatusCode StatusCode { get; set; }

        public byte[] Content { get; set; }

        public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
        {
            HttpResponseMessage response = new HttpResponseMessage(StatusCode);

            response.StatusCode = StatusCode;
            response.Content = new StreamContent(new MemoryStream(Content));
            response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
            response.Content.Headers.ContentDisposition.FileName = FileName;
            response.Content.Headers.ContentType = new MediaTypeHeaderValue(MediaType);

            return Task.FromResult(response);
        }
    }
Jake
fonte
Uma breve explicação de como seu código corrige o (s) problema (s) do OP melhoraria a qualidade de sua resposta.
Adrian Mole