Làm cách nào để trả lại tệp bằng Web API?


98

Tôi đang sử dụng API Web ASP.NET .
Tôi muốn tải xuống tệp PDF có C # từ API (API tạo ra).

Tôi có thể yêu cầu API trả về byte[]không? và đối với ứng dụng C #, tôi có thể làm:

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

File.WriteAllBytes()?

"API web"? Chính xác ý của bạn là gì? Vui lòng đọc tinyurl.com/so-hints và chỉnh sửa câu hỏi của bạn.
Jon Skeet

1
@JonSkeet: Web API là một tính năng mới trong phiên bản mới nhất của ASP.NET. Xem asp.net/whitepapers/mvc4-release-notes#_Toc317096197
Robert Harvey

1
@Robert: Cảm ơn - thẻ làm cho nó rõ ràng hơn, mặc dù đề cập đến "API Web ASP.NET" sẽ rõ ràng hơn. Một phần lỗi của MS cho một tên rubbishly generic quá :)
Jon Skeet


Bất cứ ai, rồi đến muốn trả lại luồng qua api web và IHTTPActionResult sau đó xem ở đây: nodogmablog.bryanhogan.net/2017/02/...
IbrarMumtaz

Câu trả lời:


170

Tốt hơn nên trả lại HttpResponseMessage với StreamContent bên trong nó.

Đây là ví dụ:

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 từ nhận xét của patridge : Nếu bất kỳ ai khác đến đây tìm cách gửi phản hồi từ mảng byte thay vì tệp thực, bạn sẽ muốn sử dụng ByteArrayContent (someData) mới thay vì StreamContent (xem tại đây ).


1
Điều đầu tiên - mã này sẽ gây ra một ngoại lệ vì bạn đang tạo mới hai đối tượng FileStream được trỏ vào cùng một tệp. Điều thứ hai là bạn không muốn sử dụng câu lệnh "Đang sử dụng", bởi vì ngay sau khi biến vượt ra khỏi phạm vi, .NET sẽ loại bỏ nó và bạn sẽ nhận được thông báo lỗi về việc kết nối cơ bản bị đóng.
Brandon Montgomery,

48
Nếu bất kỳ ai khác đến đây tìm cách gửi phản hồi từ một mảng byte thay vì một tệp thực tế, bạn sẽ muốn sử dụng new ByteArrayContent(someData)thay vì StreamContent(xem tại đây ).
patridge

Bạn cũng có thể muốn ghi đè cơ sở dispose () để bạn có thể xử lý tài nguyên của mình một cách chính xác khi khuôn khổ gọi nó.
Phil Cooper

1
Tôi muốn chỉ ra rằng MediaTypeHeaderValue chính xác là rất quan trọng và để làm cho nó động nếu bạn có các loại tệp khác nhau, bạn có thể làm như thế này. (trong đó fileName là một chuỗi và có kiểu tệp kết thúc như .jpg, .pdf, docx, v.v.) var contentType = MimeMapping.GetMimeMapping (fileName); response.Content.Headers.ContentType = new MediaTypeHeaderValue (contentType);
JimiSweden 19/07/2016

1
FileStream có được xử lý tự động không?
Brian Tacker

37

Tôi đã thực hiện hành động sau:

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

Điều này thực sự trả lời câu hỏi
Mick

1
Điều này sẽ không phải là một ý tưởng hay với các tệp lớn vì nó tải toàn bộ hình ảnh vào bộ nhớ. Tùy chọn luồng tốt hơn.
Paul Reedy

@PaulReedy Perfect ... nhưng trong nhiều trường hợp, bạn không cần phải xử lý các tệp lớn. Nhưng tôi hoàn toàn đồng ý với quan điểm của bạn!
André de Mattos Ferraz

14

Ví dụ với 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;
}

Nếu bạn không muốn tải xuống PDF và sử dụng trình duyệt được tích hợp sẵn trong trình xem PDF, hãy xóa hai dòng sau:

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

@ElbertJohnFelipe Có, bạn lấy tệp bằng var file = GetFile(id);. file.SomeDatalà một Mảng Byte ( byte[]) và file.FileNamestring.
Ogglas

Cảm ơn vì bài đăng của bạn. 'HttpResponseMessage' không hoạt động với tôi trong ApiController, vì vậy bạn đã cứu tôi.
Tối đa

13

Chỉ cần một lưu ý cho .Net Core: Chúng tôi có thể sử dụng FileContentResultvà đặt contentType thành application/octet-streamnếu chúng tôi muốn gửi các byte thô. Thí dụ:

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

1
Điều này hoạt động tuyệt vời, Ngoài ra nếu bạn muốn kiểm soát tên tệp, có một thuộc tính FileContentResultđược gọi FileDownloadNameđể chỉ định tên tệp
weekdev

@weeksdev ah không biết điều đó. Cảm ơn đã nhận xét.
Amir Shirazi

Đó là nó, cảm ơn. Cũng bình luận từ tuầndev là rất hữu ích.
fragg

1

Tôi đã tự hỏi liệu có cách nào đơn giản để tải xuống tệp theo cách ... "chung chung" hơn không. Tôi đã nghĩ ra điều này.

Nó đơn giản ActionResultcho phép bạn tải xuống tệp từ lệnh gọi bộ điều khiển trả về IHttpActionResult. Tệp được lưu trữ trong byte[] Content. Bạn có thể biến nó thành một luồng nếu cần.

Tôi đã sử dụng điều này để trả về các tệp được lưu trữ trong cột varbinary của cơ sở dữ liệu.

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

Giải thích ngắn gọn về cách mã của bạn khắc phục (các) vấn đề của OP sẽ nâng cao chất lượng câu trả lời của bạn.
Adrian Mole
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.