Cách trả lại tệp (FileContentResult) trong ASP.NET WebAPI


173

Trong một bộ điều khiển MVC thông thường, chúng ta có thể xuất pdf bằng a FileContentResult.

public FileContentResult Test(TestViewModel vm)
{
    var stream = new MemoryStream();
    //... add content to the stream.

    return File(stream.GetBuffer(), "application/pdf", "test.pdf");
}

Nhưng làm thế nào chúng ta có thể thay đổi nó thành một ApiController?

[HttpPost]
public IHttpActionResult Test(TestViewModel vm)
{
     //...
     return Ok(pdfOutput);
}

Đây là những gì tôi đã thử nhưng nó dường như không hoạt động.

[HttpGet]
public IHttpActionResult Test()
{
    var stream = new MemoryStream();
    //...
    var content = new StreamContent(stream);
    content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
    content.Headers.ContentLength = stream.GetBuffer().Length;
    return Ok(content);            
}

Kết quả trả về được hiển thị trong trình duyệt là:

{"Headers":[{"Key":"Content-Type","Value":["application/pdf"]},{"Key":"Content-Length","Value":["152844"]}]}

Và có một bài tương tự trên SO: Trả lại tệp nhị phân từ bộ điều khiển trong ASP.NET Web API . Nó nói về đầu ra một tập tin hiện có. Nhưng tôi không thể làm cho nó hoạt động với một luồng.

Bất kỳ đề xuất?


1
Bài đăng này đã giúp tôi: stackoverflow.com/a/23768883/585552
Greg

Câu trả lời:


199

Thay vì trở lại StreamContentnhư Content, tôi có thể làm cho nó hoạt động với ByteArrayContent.

[HttpGet]
public HttpResponseMessage Generate()
{
    var stream = new MemoryStream();
    // processing the stream.

    var result = new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = new ByteArrayContent(stream.ToArray())
    };
    result.Content.Headers.ContentDisposition =
        new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
    {
        FileName = "CertificationCard.pdf"
    };
    result.Content.Headers.ContentType =
        new MediaTypeHeaderValue("application/octet-stream");

    return result;
}

2
Nếu nửa trên trả lời câu hỏi của bạn, xin vui lòng chỉ gửi đó là một câu trả lời. Nửa thứ hai dường như là một câu hỏi khác - đăng một câu hỏi mới cho điều đó.
gunr2171

3
Xin chào, cảm ơn bạn đã chia sẻ, có một câu hỏi đơn giản (tôi đoán). Tôi có một giao diện người dùng C # nhận được thông báo. Làm cách nào để trích xuất bộ điều khiển luồng và làm cho nó có sẵn để người dùng có thể lưu nó vào đĩa hoặc thứ gì đó (và tôi có thể lấy tệp thực tế)? Cảm ơn!
Ronald

7
Tôi đã cố tải xuống một tệp excel tự tạo. Sử dụng stream.GetBuffer () luôn trả về một excel bị hỏng. Nếu thay vào đó tôi sử dụng stream.ToArray () thì tệp được tạo mà không gặp vấn đề gì. Hy vọng điều này sẽ giúp được ai đó.
afnpires

4
@AlexandrePires Đó là bởi vì MemoryStream.GetBuffer()thực sự trả về bộ đệm của MemoryStream, thường lớn hơn nội dung của luồng (để làm cho hiệu quả chèn thêm). MemoryStream.ToArray()trả về bộ đệm bị cắt cụt về kích thước nội dung.
M.Stramm

19
Hãy ngừng làm điều này. Loại lạm dụng này gây ra MemoryStream, mã không thể quét được và hoàn toàn bỏ qua mục đích của Luồng. Hãy suy nghĩ: tại sao mọi thứ không chỉ được phơi bày dưới dạng byte[]bộ đệm? Người dùng của bạn có thể dễ dàng chạy ứng dụng của bạn ra khỏi bộ nhớ.
makhdumi

97

Nếu bạn muốn trở về, IHttpActionResultbạn có thể làm như thế này:

[HttpGet]
public IHttpActionResult Test()
{
    var stream = new MemoryStream();

    var result = new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = new ByteArrayContent(stream.GetBuffer())
    };
    result.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
    {
        FileName = "test.pdf"
    };
    result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");

    var response = ResponseMessage(result);

    return response;
}

3
Cập nhật tốt để hiển thị loại trả về IHttpActionResult. Một cấu trúc lại của mã này sẽ là di chuyển gọi một IHttpActionResult tùy chỉnh, chẳng hạn như một mã được liệt kê tại: stackoverflow.com/questions/23768596/
Josh

Bài đăng này cho thấy một thực hiện sử dụng duy nhất gọn gàng tốt đẹp. Trong trường hợp của tôi, phương thức trợ giúp được liệt kê trong liên kết ở trên tỏ ra hữu ích hơn
hanzolo

45

Câu hỏi này đã giúp tôi.

Vì vậy, hãy thử điều này:

Mã điều khiển:

[HttpGet]
public HttpResponseMessage Test()
{
    var path = System.Web.HttpContext.Current.Server.MapPath("~/Content/test.docx");;
    HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
    var stream = new FileStream(path, FileMode.Open);
    result.Content = new StreamContent(stream);
    result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
    result.Content.Headers.ContentDisposition.FileName = Path.GetFileName(path);
    result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
    result.Content.Headers.ContentLength = stream.Length;
    return result;          
}

Xem đánh dấu Html (với sự kiện nhấp chuột và url đơn giản):

<script type="text/javascript">
    $(document).ready(function () {
        $("#btn").click(function () {
            // httproute = "" - using this to construct proper web api links.
            window.location.href = "@Url.Action("GetFile", "Data", new { httproute = "" })";
        });
    });
</script>


<button id="btn">
    Button text
</button>

<a href=" @Url.Action("GetFile", "Data", new { httproute = "" }) ">Data</a>

1
Ở đây bạn đang sử dụng FileStreamcho một tập tin hiện có trên máy chủ. Nó là một chút khác nhau từ MemoryStream. Nhưng cảm ơn cho đầu vào.
Blaise

4
Nếu bạn đọc từ một tệp trên máy chủ web, hãy chắc chắn sử dụng quá tải cho FileShare.Read, nếu không bạn có thể gặp phải tệp trong trường hợp ngoại lệ sử dụng.
Jeremy Bell

Nếu bạn thay thế nó bằng luồng bộ nhớ thì nó sẽ không hoạt động?
aleha

@JeremyBell nó chỉ là một ví dụ mô phỏng, không ai nói ở đây về phiên bản sản xuất và không an toàn.
aleha

1
@Blaise Xem bên dưới để biết lý do tại sao mã này hoạt động FileStreamnhưng không thành công MemoryStream. Về cơ bản nó phải làm với Stream Position.
M.Stramm

9

Đây là một triển khai giúp truyền phát nội dung của tệp mà không cần đệm (đệm theo byte [] / MemoryStream, v.v. có thể là sự cố máy chủ nếu đó là tệp lớn).

public class FileResult : IHttpActionResult
{
    public FileResult(string filePath)
    {
        if (filePath == null)
            throw new ArgumentNullException(nameof(filePath));

        FilePath = filePath;
    }

    public string FilePath { get; }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        var response = new HttpResponseMessage(HttpStatusCode.OK);
        response.Content = new StreamContent(File.OpenRead(FilePath));
        var contentType = MimeMapping.GetMimeMapping(Path.GetExtension(FilePath));
        response.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
        return Task.FromResult(response);
    }
}

Nó có thể được sử dụng đơn giản như thế này:

public class MyController : ApiController
{
    public IHttpActionResult Get()
    {
        string filePath = GetSomeValidFilePath();
        return new FileResult(filePath);
    }
}

Làm thế nào bạn sẽ xóa các tập tin sau khi tải xuống được thực hiện? Có bất kỳ hook nào được thông báo khi quá trình tải xuống kết thúc không?
costa

ok, câu trả lời dường như là thực hiện một thuộc tính bộ lọc hành động và loại bỏ tệp trong phương thức OnActionExecuted.
costa

5
Tìm thấy bài đăng này Câu trả lời của Risord: stackoverflow.com/questions/2041717/ cấp . Người ta có thể sử dụng dòng này var fs = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.None, 4096, FileOptions.DeleteOnClose);thay vìFile.OpenRead(FilePath)
costa

7

Tôi không chắc chắn chính xác phần nào để đổ lỗi, nhưng đây là lý do tại sao MemoryStreamkhông làm việc cho bạn:

Khi bạn viết thư MemoryStream, nó sẽ tăng Positiontài sản của nó . Hàm tạo của StreamContenttính đến hiện tại của luồng Position. Vì vậy, nếu bạn viết vào luồng, sau đó chuyển nó sang StreamContent, phản hồi sẽ bắt đầu từ hư vô ở cuối luồng.

Có hai cách để khắc phục điều này:

1) xây dựng nội dung, ghi vào luồng

[HttpGet]
public HttpResponseMessage Test()
{
    var stream = new MemoryStream();
    var response = Request.CreateResponse(HttpStatusCode.OK);
    response.Content = new StreamContent(stream);
    // ...
    // stream.Write(...);
    // ...
    return response;
}

2) ghi vào luồng, đặt lại vị trí, xây dựng nội dung

[HttpGet]
public HttpResponseMessage Test()
{
    var stream = new MemoryStream();
    // ...
    // stream.Write(...);
    // ...
    stream.Position = 0;

    var response = Request.CreateResponse(HttpStatusCode.OK);
    response.Content = new StreamContent(stream);
    return response;
}

2) có vẻ tốt hơn một chút nếu bạn có Luồng mới, 1) đơn giản hơn nếu luồng của bạn không bắt đầu từ 0


Mã này thực sự không cung cấp bất kỳ giải pháp nào cho vấn đề, vì nó sử dụng cùng một cách tiếp cận đã được đề cập trong câu hỏi. Câu hỏi đã nói rằng điều này không hoạt động, và tôi có thể xác nhận điều đó. return Ok (new StreamContent (stream)) trả về đại diện JSON của StreamContent.
Dmytro Zakharov

Cập nhật mã. Câu trả lời này thực sự trả lời câu hỏi tinh tế hơn về 'tại sao giải pháp đơn giản lại hoạt động với FileStream mà không phải là MemoryStream' hơn là cách trả về một tệp trong WebApi.
M.Stramm

3

Đối với tôi đó là sự khác biệt giữa

var response = Request.CreateResponse(HttpStatusCode.OK, new StringContent(log, System.Text.Encoding.UTF8, "application/octet-stream");

var response = Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent(log, System.Text.Encoding.UTF8, "application/octet-stream");

Cái đầu tiên là trả về đại diện JSON của StringContent: {"Headers": [{"Key": "Content-Type", "Value": ["application / octet-stream; charset = utf-8"]}]}

Trong khi cái thứ hai đã trả lại tập tin thích hợp.

Dường như Request.CreateResponse có tình trạng quá tải, lấy một chuỗi làm tham số thứ hai và đây dường như là nguyên nhân khiến chính đối tượng StringContent được hiển thị dưới dạng chuỗi, thay vì nội dung thực tế.

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.