Come restituire un file utilizzando l'API Web?


100

Utilizzo l' API Web ASP.NET .
Voglio scaricare un PDF con C # dall'API (che l'API genera).

Posso semplicemente chiedere all'API di restituire un byte[]? e per l'applicazione C # posso semplicemente fare:

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

e

File.WriteAllBytes()?

"l'API Web"? Cosa intendi esattamente? Leggi tinyurl.com/so-hints e modifica la tua domanda.
Jon Skeet

1
@ JonSkeet: l'API Web è una nuova funzionalità nell'ultima versione di ASP.NET. Vedi asp.net/whitepapers/mvc4-release-notes#_Toc317096197
Robert Harvey,

1
@Robert: Grazie - il tag lo rende più chiaro, anche se fare riferimento all '"API Web ASP.NET" sarebbe stato ancora più chiaro. In parte anche colpa di MS per un nome schifosamente generico :)
Jon Skeet


Chiunque atterra volendo restituire lo stream tramite web api e IHTTPActionResult può vedere qui: nodogmablog.bryanhogan.net/2017/02/…
IbrarMumtaz

Risposte:


172

Meglio restituire HttpResponseMessage con StreamContent al suo interno.

Ecco un esempio:

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 dal commento di patridge : se qualcun altro dovesse arrivare qui cercando di inviare una risposta da un array di byte invece di un file effettivo, vorrai usare il nuovo ByteArrayContent (someData) invece di StreamContent (vedi qui ).


1
Prima cosa: questo codice causerà un'eccezione poiché stai aggiornando due oggetti FileStream puntati sullo stesso file. La seconda cosa è che non si desidera utilizzare un'istruzione "Using", perché non appena la variabile esce dall'ambito, .NET la eliminerà e riceverai messaggi di errore sulla chiusura della connessione sottostante.
Brandon Montgomery,

48
Se qualcun altro dovesse arrivare qui cercando di inviare una risposta da un array di byte invece di un file effettivo, vorrai usare al new ByteArrayContent(someData)posto di StreamContent(vedi qui ).
patridge

Potresti anche voler sovrascrivere il dispose () di base in modo da poter gestire correttamente le tue risorse quando il framework lo chiama.
Phil Cooper

2
Vorrei sottolineare che il corretto MediaTypeHeaderValue è cruciale e per renderlo dinamico se hai diversi tipi di file puoi fare in questo modo. (dove fileName è una stringa e ha un tipo di file che termina come .jpg, .pdf, docx ecc.) var contentType = MimeMapping.GetMimeMapping (fileName); response.Content.Headers.ContentType = new MediaTypeHeaderValue (contentType);
JimiSweden

1
FileStream viene eliminato automaticamente?
Brian Tacker

37

Ho eseguito la seguente azione:

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

Questo in realtà risponde alla domanda
Mick

1
Questa non sarebbe una buona idea con file di grandi dimensioni poiché carica l'intera immagine in memoria. L'opzione stream è migliore.
Paul Reedy,

@PaulReedy Perfetto ... ma in molti casi non è necessario gestire file di grandi dimensioni. Ma sono totalmente d'accordo con il tuo punto!
André de Mattos Ferraz

15

Esempio con IHttpActionResultin 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 non desideri scaricare il PDF e utilizzare un browser integrato nel visualizzatore di PDF, rimuovi invece le due righe seguenti:

responseMsg.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
responseMsg.Content.Headers.ContentDisposition.FileName = file.FileName;

@ElbertJohnFelipe Sì, ottieni il file con var file = GetFile(id);. file.SomeDataè un Byte Array ( byte[]) ed file.FileNameè string.
Ogglas

Grazie per il tuo post. "HttpResponseMessage" non ha funzionato per me all'interno di un ApiController, quindi mi hai salvato.
Max

14

Solo una nota per .Net Core: possiamo usare FileContentResulte impostare contentType su application/octet-streamse vogliamo inviare i byte grezzi. Esempio:

[HttpGet("{id}")]
public IActionResult GetDocumentBytes(int id)
{
    byte[] byteArray = GetDocumentByteArray(id);
    return new FileContentResult(byteArray, "application/octet-stream");
}

1
Funziona alla grande, anche se vuoi controllare il nome del file c'è una proprietà FileContentResultchiamata FileDownloadNameper specificare il nome del file
weeksdev

@weeksdev ah non lo sapevo. Grazie per il commento.
Amir Shirazi

Questo è tutto, grazie. Anche il commento di weeksdev è molto utile.
dal

1

Mi chiedevo se esistesse un modo semplice per scaricare un file in modo più ... "generico". Ho pensato a questo.

È un semplice ActionResultche ti permetterà di scaricare un file da una chiamata del controller che restituisce un file IHttpActionResult. Il file è archiviato in byte[] Content. Puoi trasformarlo in un flusso se necessario.

Ho usato questo per restituire i file memorizzati nella colonna varbinary di un database.

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

Una breve spiegazione di come il tuo codice risolve i problemi dell'OP migliorerebbe la qualità della tua risposta.
Adrian Mole
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.