Jak zwrócić plik za pomocą Web API?

100

Używam interfejsu API sieci Web ASP.NET .
Chcę pobrać plik PDF z C # z API (który generuje API).

Czy mogę po prostu poprosić API o zwrócenie byte[]? a dla aplikacji C # mogę po prostu zrobić:

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

i

File.WriteAllBytes()?
Kyle
źródło
„Internetowy interfejs API”? Co dokładnie masz na myśli? Przeczytaj tinyurl.com/so-hints i edytuj swoje pytanie.
Jon Skeet
1
@JonSkeet: Web API to nowa funkcja w najnowszej wersji programu ASP.NET. Zobacz stronę asp.net/whitepapers/mvc4-release-notes#_Toc317096197
Robert Harvey
1
@Robert: Dzięki - tag czyni go bardziej przejrzystym, chociaż odwoływanie się do „internetowego interfejsu API ASP.NET” byłoby jeszcze jaśniejsze. Częściowo wina MS za kiepską nazwę ogólną :)
Jon Skeet
Każdy, kto wyląduje, chcąc zwrócić strumień za pośrednictwem internetowego interfejsu API i IHTTPActionResult, zobacz tutaj: nodogmablog.bryanhogan.net/2017/02/ ...
IbrarMumtaz

Odpowiedzi:

172

Lepiej jest zwrócić HttpResponseMessage z zawartością StreamContent.

Oto przykład:

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 z komentarza patridge : Jeśli ktoś inny przyjdzie tutaj, aby wysłać odpowiedź z tablicy bajtów zamiast rzeczywistego pliku, będziesz chciał użyć nowego ByteArrayContent (someData) zamiast StreamContent (patrz tutaj ).

Regfor
źródło
1
Po pierwsze - ten kod spowoduje wyjątek, ponieważ tworzysz nowe dwa obiekty FileStream wskazujące na ten sam plik. Drugą rzeczą jest to, że nie chcesz używać instrukcji „Using”, ponieważ gdy tylko zmienna wyjdzie poza zakres, .NET usunie ją i otrzymasz komunikaty o błędach dotyczące zamykania podstawowego połączenia.
Brandon Montgomery
48
Gdyby ktoś inny przyszedł tutaj i chciał wysłać odpowiedź z tablicy bajtów zamiast z rzeczywistego pliku, będziesz chciał użyć new ByteArrayContent(someData)zamiast StreamContent(patrz tutaj ).
patridge
Możesz również chcieć przesłonić metodę base dispose (), aby móc poprawnie obsługiwać zasoby, gdy środowisko ją wywołuje.
Phil Cooper,
2
Chciałbym zwrócić uwagę, że poprawna wartość MediaTypeHeaderValue jest kluczowa i aby uzyskać dynamikę, jeśli masz różne typy plików, możesz to zrobić. (gdzie nazwa_pliku jest ciągiem i ma typ pliku kończący się np. .jpg, .pdf, docx itp ..) var contentType = MimeMapping.GetMimeMapping (fileName); response.Content.Headers.ContentType = new MediaTypeHeaderValue (contentType);
JimiSweden
1
Czy FileStream jest usuwany automatycznie?
Brian Tacker
37

Wykonałem następującą akcję:

[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
źródło
To właściwie odpowiada na pytanie
Mick
1
Nie byłby to dobry pomysł w przypadku dużych plików, ponieważ ładuje cały obraz do pamięci. Opcja transmisji jest lepsza.
Paul Reedy
@PaulReedy Perfect ... ale w wielu przypadkach nie musisz zajmować się dużymi plikami. Ale całkowicie się z tobą zgadzam!
André de Mattos Ferraz
15

Przykład z 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;
}

Jeśli nie chcesz pobierać pliku PDF i korzystać z przeglądarki wbudowanej w przeglądarkę PDF, zamiast tego usuń następujące dwie linie:

responseMsg.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
responseMsg.Content.Headers.ContentDisposition.FileName = file.FileName;
Ogglas
źródło
@ElbertJohnFelipe Tak, otrzymujesz plik z rozszerzeniem var file = GetFile(id);. file.SomeDatajest Byte Array ( byte[]) i file.FileNamejest string.
Ogglas
Dziękuję za wiadomość. „HttpResponseMessage” nie działał dla mnie w ApiController, więc mnie uratowałeś.
Maksymalnie
14

Uwaga dla .Net Core: Możemy użyć FileContentResulti ustawić contentType na, application/octet-streamjeśli chcemy wysłać nieprzetworzone bajty. Przykład:

[HttpGet("{id}")]
public IActionResult GetDocumentBytes(int id)
{
    byte[] byteArray = GetDocumentByteArray(id);
    return new FileContentResult(byteArray, "application/octet-stream");
}
Amir Shirazi
źródło
1
To działa świetnie, również jeśli chcesz kontrolować nazwę pliku, istnieje właściwość na FileContentResultwezwanie FileDownloadNamedo określenia nazwy pliku
weekdev
@weeksdev ah tego nie wiedział. Dziękuję za komentarz.
Amir Shirazi
To wszystko, dzięki. Bardzo przydatny jest również komentarz z weekdev.
fragg
1

Zastanawiałem się, czy istnieje prosty sposób na pobranie pliku w bardziej… „ogólny” sposób. Wymyśliłem to.

Jest to proste ActionResult, które pozwoli ci pobrać plik z wywołania kontrolera, które zwraca plik IHttpActionResult. Plik jest przechowywany w formacie byte[] Content. W razie potrzeby możesz przekształcić go w strumień.

Użyłem tego do zwrócenia plików przechowywanych w kolumnie varbinary bazy danych.

    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
źródło
Krótkie wyjaśnienie, w jaki sposób twój kod rozwiązuje problem (y) PO, poprawi jakość twojej odpowiedzi.
Adrian Mole