Wie kann ich eine Datei mit der Web-API zurückgeben?

98

Ich verwende die ASP.NET-Web-API .
Ich möchte ein PDF mit C # von der API herunterladen (die die API generiert).

Kann ich die API einfach a zurückgeben lassen byte[]? und für die C # -Anwendung kann ich einfach tun:

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

und

File.WriteAllBytes()?
Kyle
quelle
"die Web-API"? Was genau meinst du? Bitte lesen Sie tinyurl.com/so-hints und bearbeiten Sie Ihre Frage.
Jon Skeet
1
@ JonSkeet: Die Web-API ist eine neue Funktion in der neuesten Version von ASP.NET. Siehe asp.net/whitepapers/mvc4-release-notes#_Toc317096197
Robert Harvey
1
@ Robert: Danke - das Tag macht es klarer, obwohl der Verweis auf "die ASP.NET-Web-API" noch klarer gewesen wäre. Teilweise MS's Schuld für einen blöd generischen Namen auch :)
Jon Skeet
Jeder, der landet und den Stream über Web-API und IHTTPActionResult zurückgeben möchte, sieht hier: nodogmablog.bryanhogan.net/2017/02/…
IbrarMumtaz

Antworten:

170

Es ist besser, HttpResponseMessage mit StreamContent zurückzugeben.

Hier ist ein Beispiel:

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 von Kommentar von patridge : Sollte jemand anderes hierher kommen, um eine Antwort von einem Byte-Array anstelle einer tatsächlichen Datei zu senden, sollten Sie anstelle von StreamContent einen neuen ByteArrayContent (someData) verwenden (siehe hier ).

Regfor
quelle
1
Als erstes verursacht dieser Code eine Ausnahme, da Sie zwei FileStream-Objekte neu erstellen, die auf dieselbe Datei zeigen. Zweitens möchten Sie keine "Using" -Anweisung verwenden, da .NET sie entsorgt, sobald die Variable den Gültigkeitsbereich verlässt, und Sie Fehlermeldungen erhalten, dass die zugrunde liegende Verbindung geschlossen wird.
Brandon Montgomery
48
Sollte jemand anderes hierher kommen, um eine Antwort von einem Byte-Array anstelle einer tatsächlichen Datei zu senden, möchten Sie new ByteArrayContent(someData)stattdessen StreamContent(siehe hier ) verwenden.
Patridge
Möglicherweise möchten Sie auch die Basisdispose () überschreiben, damit Sie Ihre Ressourcen korrekt behandeln können, wenn das Framework sie aufruft.
Phil Cooper
1
Ich möchte darauf hinweisen, dass der richtige MediaTypeHeaderValue von entscheidender Bedeutung ist und dass Sie dies dynamisch tun können, wenn Sie unterschiedliche Dateitypen haben. (wobei Dateiname eine Zeichenfolge ist und einen Dateityp hat, der wie .jpg, .pdf, docx usw. endet.) var contentType = MimeMapping.GetMimeMapping (Dateiname); response.Content.Headers.ContentType = neuer MediaTypeHeaderValue (contentType);
JimiSweden
1
Wird der FileStream automatisch entsorgt?
Brian Tacker
37

Ich habe folgende Aktion ausgeführt:

[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
quelle
Dies beantwortet tatsächlich die Frage
Mick
1
Dies ist bei großen Dateien keine gute Idee, da das gesamte Bild in den Speicher geladen wird. Die Stream-Option ist besser.
Paul Reedy
@PaulReedy Perfekt ... aber in vielen Fällen müssen Sie sich nicht mit großen Dateien befassen. Aber ich stimme Ihrem Standpunkt voll und ganz zu!
André de Mattos Ferraz
14

Beispiel mit IHttpActionResultinApiController .

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

Wenn Sie die PDF-Datei nicht herunterladen und einen im PDF-Viewer integrierten Browser verwenden möchten, entfernen Sie stattdessen die folgenden zwei Zeilen:

responseMsg.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
responseMsg.Content.Headers.ContentDisposition.FileName = file.FileName;
Ogglas
quelle
@ElbertJohnFelipe Ja, Sie erhalten die Datei mit var file = GetFile(id);. file.SomeDataist ein Byte Array ( byte[]) und file.FileNameist string.
Ogglas
Vielen Dank für Ihren Beitrag. 'HttpResponseMessage' hat in einem ApiController nicht für mich funktioniert, also haben Sie mich gerettet.
Max
13

Nur ein Hinweis für .Net Core: Wir können das verwenden FileContentResultund den contentType auf setzen, application/octet-streamwenn wir die Rohbytes senden möchten. Beispiel:

[HttpGet("{id}")]
public IActionResult GetDocumentBytes(int id)
{
    byte[] byteArray = GetDocumentByteArray(id);
    return new FileContentResult(byteArray, "application/octet-stream");
}
Amir Shirazi
quelle
1
Dies funktioniert hervorragend. Auch wenn Sie den Dateinamen FileContentResultFileDownloadName
steuern möchten,
@weeksdev ah wusste das nicht. Danke für den Kommentar.
Amir Shirazi
Das war's, danke. Auch ein Kommentar von weekdev ist sehr nützlich.
Fragg
1

Ich habe mich gefragt, ob es eine einfache Möglichkeit gibt, eine Datei auf eine "allgemeinere" Weise herunterzuladen. Ich habe mir das ausgedacht.

Es ist eine einfache ActionResultMethode, mit der Sie eine Datei von einem Controller-Aufruf herunterladen können, der eine zurückgibt IHttpActionResult. Die Datei wird in der gespeichert byte[] Content. Sie können es bei Bedarf in einen Stream verwandeln.

Ich habe dies verwendet, um Dateien zurückzugeben, die in der varbinary-Spalte einer Datenbank gespeichert sind.

    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
quelle
Eine kurze Erklärung, wie Ihr Code die Probleme des OP behebt, würde die Qualität Ihrer Antwort verbessern.
Adrian Mole