Trả lại một tệp để xem / Tải xuống trong ASP.NET MVC


304

Tôi đang gặp vấn đề khi gửi các tệp được lưu trữ trong cơ sở dữ liệu lại cho người dùng trong ASP.NET MVC. Những gì tôi muốn là một khung nhìn liệt kê hai liên kết, một để xem tệp và để cho mimetype được gửi tới trình duyệt xác định cách xử lý và liên kết kia để buộc tải xuống.

Nếu tôi chọn xem một tệp được gọi SomeRandomFile.bakvà trình duyệt không có chương trình liên quan để mở các tệp thuộc loại này, thì tôi không gặp vấn đề gì với việc mặc định nó là hành vi tải xuống. Tuy nhiên, nếu tôi chọn xem một tệp được gọi SomeRandomFile.pdfhoặc SomeRandomFile.jpgtôi muốn tệp chỉ đơn giản là mở. Nhưng tôi cũng muốn giữ một liên kết tải xuống sang một bên để tôi có thể buộc một dấu nhắc tải xuống bất kể loại tệp. Điều này có nghĩa không?

Tôi đã thử FileStreamResultvà nó hoạt động với hầu hết các tệp, hàm tạo của nó không chấp nhận tên tệp theo mặc định, vì vậy các tệp không xác định được gán tên tệp dựa trên URL (không biết tiện ích mở rộng để cung cấp dựa trên loại nội dung). Nếu tôi buộc tên tệp bằng cách chỉ định nó, tôi sẽ mất khả năng trình duyệt mở tệp trực tiếp và tôi nhận được lời nhắc tải xuống. Đã có người khác gặp phải điều này?

Đây là những ví dụ về những gì tôi đã thử cho đến nay.

//Gives me a download prompt.
return File(document.Data, document.ContentType, document.Name);

//Opens if it is a known extension type, downloads otherwise (download has bogus name and missing extension)
return new FileStreamResult(new MemoryStream(document.Data), document.ContentType);

//Gives me a download prompt (lose the ability to open by default if known type)
return new FileStreamResult(new MemoryStream(document.Data), document.ContentType) {FileDownloadName = document.Name};

Bất kỳ đề xuất?


CẬP NHẬT: Câu hỏi này dường như tấn công hợp âm với rất nhiều người, vì vậy tôi nghĩ rằng tôi sẽ đăng một bản cập nhật. Cảnh báo về câu trả lời được chấp nhận dưới đây được Oskar thêm vào liên quan đến các ký tự quốc tế là hoàn toàn hợp lệ và tôi đã nhấn nó vài lần do sử dụng ContentDispositionlớp. Kể từ khi tôi cập nhật triển khai để sửa lỗi này. Mặc dù đoạn mã dưới đây là từ sự xuất hiện gần đây nhất của tôi về vấn đề này trong ứng dụng ASP.NET Core (Full Framework), nó sẽ hoạt động với những thay đổi tối thiểu trong một ứng dụng MVC cũ hơn vì tôi đang sử dụng System.Net.Http.Headers.ContentDispositionHeaderValuelớp.

using System.Net.Http.Headers;

public IActionResult Download()
{
    Document document = ... //Obtain document from database context

    //"attachment" means always prompt the user to download
    //"inline" means let the browser try and handle it
    var cd = new ContentDispositionHeaderValue("attachment")
    {
        FileNameStar = document.FileName
    };
    Response.Headers.Add(HeaderNames.ContentDisposition, cd.ToString());

    return File(document.Data, document.ContentType);
}

// an entity class for the document in my database 
public class Document
{
    public string FileName { get; set; }
    public string ContentType { get; set; }
    public byte[] Data { get; set; }
    //Other properties left out for brevity
}

Câu trả lời:


430
public ActionResult Download()
{
    var document = ...
    var cd = new System.Net.Mime.ContentDisposition
    {
        // for example foo.bak
        FileName = document.FileName, 

        // always prompt the user for downloading, set to true if you want 
        // the browser to try to show the file inline
        Inline = false, 
    };
    Response.AppendHeader("Content-Disposition", cd.ToString());
    return File(document.Data, document.ContentType);
}

LƯU Ý: Mã ví dụ trên không thể giải thích chính xác các ký tự quốc tế trong tên tệp. Xem RFC6266 để biết tiêu chuẩn hóa có liên quan. Tôi tin rằng các phiên bản gần đây của File()phương thức của ASP.Net MVC và ContentDispositionHeaderValuelớp tài khoản đúng cho việc này. - Oskar 2016 / 02-25


7
Nếu tôi nhớ lại một cách chính xác, nó có thể không được trích dẫn miễn là tên tệp không có khoảng trắng (tôi đã chạy của tôi thông qua HttpUtility.UrlEncode () để đạt được điều này).
Keith Williams

22
Lưu ý: Nếu bạn sử dụng điều này và đặt Inline = truechắc chắn KHÔNG sử dụng quá tải 3-param File()lấy tên tệp làm tham số thứ 3. Nó sẽ hoạt động trong IE, nhưng Chrome sẽ báo cáo một tiêu đề trùng lặp và từ chối trình bày hình ảnh.
Faust

74
Loại tài liệu var = ...?
TTT

7
@ user1103990, đó là mô hình miền của bạn.
Darin Dimitrov

3
Sử dụng MVC 5, không cần tiêu đề xử lý nội dung nữa, vì nó đã là một phần của tiêu đề phản hồi. Nhưng tôi chỉ nhận được hộp thoại tải xuống trong FF, không có hộp thoại nào trong chrome và IE
Huyền thoại

124

Tôi gặp rắc rối với câu trả lời được chấp nhận do không có gợi ý về biến "tài liệu": var document = ...Vì vậy, tôi đang đăng những gì làm việc cho tôi như một cách thay thế trong trường hợp bất kỳ ai khác gặp rắc rối.

public ActionResult DownloadFile()
{
    string filename = "File.pdf";
    string filepath = AppDomain.CurrentDomain.BaseDirectory + "/Path/To/File/" + filename;
    byte[] filedata = System.IO.File.ReadAllBytes(filepath);
    string contentType = MimeMapping.GetMimeMapping(filepath);

    var cd = new System.Net.Mime.ContentDisposition
    {
        FileName = filename,
        Inline = true,
    };

    Response.AppendHeader("Content-Disposition", cd.ToString());

    return File(filedata, contentType);
}

4
Biến tài liệu chỉ là một lớp (POCO) đại diện cho thông tin về tài liệu bạn muốn trả về. Điều đó đã được hỏi về câu trả lời được chấp nhận là tốt. Nó có thể đến từ ORM, từ truy vấn SQL được xây dựng thủ công, từ hệ thống tệp (khi bạn lấy thông tin từ) hoặc một số lưu trữ dữ liệu khác. Nó không liên quan đến câu hỏi ban đầu trong đó loại tài liệu / tên tệp / mime tài liệu của bạn đến từ đâu nên nó bị bỏ lại để không làm vấy bẩn mã. Cảm ơn bạn đã đóng góp một ví dụ chỉ sử dụng hệ thống tập tin.
Nick Albrecht

1
Giải pháp này không hoạt động chính xác khi tên tệp chứa các ký tự quốc tế bên ngoài US-ASCII.
Oskar Berggren

1
Cảm ơn, đã cứu ngày của tôi :)
Dipesh

1
Một thay thế AppDomain.CurrentDomain.BaseDirectorySystem.Web.HttpContext.Current.Server.MapPath("~"), điều này có thể hoạt động tốt hơn trên một máy chủ thực so với một máy cục bộ.
Chris Thompson

15

Câu trả lời của Darin Dimitrov là chính xác. Chỉ là một bổ sung:

Response.AppendHeader("Content-Disposition", cd.ToString());có thể khiến trình duyệt không thể hiển thị tệp nếu phản hồi của bạn đã chứa tiêu đề "Xử lý nội dung". Trong trường hợp đó, bạn có thể muốn sử dụng:

Response.Headers.Add("Content-Disposition", cd.ToString());

Tôi thấy rằng loại nội dung cũng có ảnh hưởng, đối với pdftệp, nếu tôi đặt loại nội dung là System.Net.Mime.MediaTypeNames.Application.Octet, nó sẽ buộc tải xuống ngay cả khi tôi đặt Inline = true, nhưng nếu tôi đặt Response.ContentType = MimeMapping.GetMimeMapping(filePath)application/pdf, nó có thể mở chính xác thay vì tải xuống
yu yang Jian

Response.Headers.Addyêu cầu IIS chế độ đường ống tích hợp. Ngoài ra, ngay cả khi nhóm ứng dụng được đặt là tích hợp, nó sẽ đưa ra một ngoại lệ. Giải pháp. Sử dụng Response.AddHeader. Xem chủ đề SO: stackoverflow.com/questions/22313167/ Cách
roland

12

Để xem tệp (ví dụ txt):

return File("~/TextFileInRootDir.txt", MediaTypeNames.Text.Plain);

Để tải xuống tệp (ví dụ txt):

return File("~/TextFileInRootDir.txt", MediaTypeNames.Text.Plain, "TextFile.txt");

lưu ý: để tải xuống tệp, chúng ta nên truyền đối số fileD DownloadName


Vấn đề duy nhất với phương pháp này là tên tệp chỉ được cung cấp nếu bạn sử dụng phương pháp buộc tải xuống. Nó không cho phép tôi gửi tên tệp chính xác khi tôi muốn để trình duyệt xác định cách mở tệp. Vì vậy, ứng dụng bên ngoài sẽ cố gắng sử dụng URL của tôi làm tên tệp. Mà thường sẽ là một số loại id cho tài liệu trong cơ sở dữ liệu, thường không có phần mở rộng tệp. Điều này làm cho một cái tên khá nghèo nàn không khớp với URL được sử dụng để truy cập tệp, vì kịch bản là nếu bạn không cung cấp.
Nick Albrecht

Sử dụng thuộc Inlinetính trên Bố trí nội dung cho phép tôi tách biệt khả năng đặt tên tệp khỏi hành vi buộc tải xuống hay không.
Nick Albrecht

4

Tôi tin rằng câu trả lời này sạch hơn, (dựa trên https://stackoverflow.com/a/3007668/550975 )

    public ActionResult GetAttachment(long id)
    {
        FileAttachment attachment;
        using (var db = new TheContext())
        {
            attachment = db.FileAttachments.FirstOrDefault(x => x.Id == id);
        }

        return File(attachment.FileData, "application/force-download", Path.GetFileName(attachment.FileName));
    }

9
Không được đề xuất, Xử lý nội dung là phương pháp được ưa thích vì sự rõ ràng và tương thích. stackoverflow.com/questions/10615797/ Mạnh
Nick Albrecht

Cảm ơn vì điều đó. Mặc dù tôi đã thay đổi loại MIME thành application/octet-streamvà điều đó vẫn khiến tệp được tải xuống thay vì hiển thị và có vẻ như nó tương thích.
Serj Sagan

1
Nói dối về loại nội dung nghe có vẻ là một ý tưởng thực sự tồi tệ. Một số trình duyệt phụ thuộc vào loại nội dung chính xác để đề xuất các ứng dụng cho người dùng trong hộp thoại "lưu hoặc mở".
Oskar Berggren

Nếu mục đích của bạn là để trình duyệt đề xuất một ứng dụng thì không sao, nhưng câu hỏi này cụ thể là về việc buộc tải xuống ...
Serj Sagan

@SerjSagan Tôi nghĩ nhiều hơn về việc tránh hành vi mặc định của trình duyệt để xem trực tiếp các loại tệp nhất định hoặc sử dụng plugin, thay vì đưa ra lựa chọn giữa lưu / mở. Không thử với ví dụ JPEG ngay bây giờ, vì vậy không chắc chắn về hành vi chính xác.
Oskar Berggren

3

FileVirtualPath -> Nghiên cứu \ Đánh giá văn phòng toàn cầu.pdf

public virtual ActionResult GetFile()
{
    return File(FileVirtualPath, "application/force-download", Path.GetFileName(FileVirtualPath));
}

5
Điều này đã được đề cập trong câu trả lời trước và không được khuyến khích. Xem câu hỏi sau để biết thêm chi tiết về lý do tại sao. stackoverflow.com/questions/10615797/ Mạnh
Nick Albrecht

1
Bằng cách này, nó không sử dụng tài nguyên trên máy chủ bằng cách tải tệp vào bộ nhớ trên máy chủ, tôi có đúng không?
Ian Jowett

1
Tôi tin là như vậy, vì không cần bạn phải không @CrashOverride
Bishoy Hanna

1

Mã bên dưới làm việc cho tôi để lấy tệp pdf từ dịch vụ API và phản hồi nó ra trình duyệt - hy vọng nó có ích;

public async Task<FileResult> PrintPdfStatements(string fileName)
    {
         var fileContent = await GetFileStreamAsync(fileName);
         var fileContentBytes = ((MemoryStream)fileContent).ToArray();
         return File(fileContentBytes, System.Net.Mime.MediaTypeNames.Application.Pdf);
    }

2
Cảm ơn câu trả lời. Bạn đã bỏ lỡ phần mà tôi đang tìm kiếm một giải pháp bao gồm khả năng chỉ định tên của tệp. Bạn chỉ trả về byte, vì vậy tên sẽ được suy ra từ URL. Tôi đã sử dụng PDF làm ví dụ, nhưng tôi cần nó để làm việc cho nhiều loại tệp khác trong trường hợp của tôi. Tôi nên đề cập rằng giải pháp của bạn sẽ hoạt động miễn là bạn đang sử dụng .NET Framework 4.x và MVC <= 5. Nếu bạn đang chạy với .NET Core, cách tốt nhất của bạn là sử dụngMicrosoft.AspNetCore.StaticFiles.FileExtensionContentTypeProvider
Nick Albrecht

@NickAlbrecht Tôi không sử dụng .Net Core - ví dụ trên là cho phản hồi pdf trên trình duyệt. Tuy nhiên, nếu bạn muốn tải xuống hãy thử: File (fileContentBytes, System.Net.Mime.MediaTypeNames.Application.Pdf, "tên tệp của bạn"). Tôi không chắc chắn mặc dù nếu bạn có câu trả lời của bạn. vui lòng cho tôi biết nếu điều này có ích. Cảm ơn về đề xuất .Net Core mặc dù. Ngoài ra nếu bạn thấy câu trả lời của tôi hữu ích, vui lòng thêm phiếu bầu.
Cậu bé Jonny

Tôi đã giải quyết vấn đề từ lâu với câu trả lời tôi đã đánh dấu là đã chấp nhận. Tôi đã chỉ ra một vài cảnh báo trong trường hợp bạn thấy chúng hữu ích. Câu hỏi ban đầu của tôi là năm 2011, vì vậy bây giờ nó khá là cũ.
Nick Albrecht

@NickAlbrecht Cảm ơn bạn. Tôi không biết bạn đã giải quyết nó, tôi đã thấy đó là một bài viết rất cũ. Tôi tìm thấy diễn đàn này trong khi tìm kiếm một số câu trả lời liên quan đến async, tôi chưa quen với các quy trình async. tôi đã có thể giải quyết vấn đề của mình vì vậy chỉ cần chia sẻ. Hơn bạn cho thời gian của bạn.
Cậu bé Jonny

0

Phương thức hành động cần trả về FileResult bằng một luồng, byte [] hoặc đường dẫn ảo của tệp. Bạn cũng sẽ cần biết loại nội dung của tệp đang được tải xuống. Đây là một phương pháp tiện ích mẫu (nhanh / bẩn). Liên kết video mẫu Cách tải xuống tệp bằng lõi asp.net

[Route("api/[controller]")]
public class DownloadController : Controller
{
    [HttpGet]
    public async Task<IActionResult> Download()
    {
        var path = @"C:\Vetrivel\winforms.png";
        var memory = new MemoryStream();
        using (var stream = new FileStream(path, FileMode.Open))
        {
            await stream.CopyToAsync(memory);
        }
        memory.Position = 0;
        var ext = Path.GetExtension(path).ToLowerInvariant();
        return File(memory, GetMimeTypes()[ext], Path.GetFileName(path));
    }

    private Dictionary<string, string> GetMimeTypes()
    {
        return new Dictionary<string, string>
        {
            {".txt", "text/plain"},
            {".pdf", "application/pdf"},
            {".doc", "application/vnd.ms-word"},
            {".docx", "application/vnd.ms-word"},
            {".png", "image/png"},
            {".jpg", "image/jpeg"},
            ...
        };
    }
}

Liên kết đến một cái gì đó bạn liên kết với (ví dụ: thư viện, công cụ, sản phẩm, hướng dẫn hoặc trang web) mà không tiết lộ nó là của bạn bị coi là thư rác trên Stack Overflow. Xem: Điều gì biểu thị sự tự quảng cáo "Tốt"? , một số mẹo và lời khuyên về tự quảng cáo , định nghĩa chính xác của "thư rác" cho Stack Overflow là gì? những gì làm cho một cái gì đó thư rác .
Samuel Liew

0

Nếu, giống như tôi, bạn đã đến chủ đề này thông qua các thành phần của Dao cạo khi bạn đang học Blazor, thì bạn sẽ thấy bạn cần phải suy nghĩ thêm một chút bên ngoài để giải quyết vấn đề này. Đó là một chút của một bãi mìn nếu (cũng như tôi) Blazor là người đầu tiên bạn bước vào thế giới kiểu MVC, vì tài liệu này không hữu ích cho các nhiệm vụ 'nam tính' như vậy.

Vì vậy, tại thời điểm viết bài, bạn không thể đạt được điều này bằng cách sử dụng vanilla Blazor / Dao cạo mà không nhúng bộ điều khiển MVC để xử lý phần tải xuống tệp, một ví dụ như sau:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;

[Route("api/[controller]")]
[ApiController]
public class FileHandlingController : ControllerBase
{
    [HttpGet]
    public FileContentResult Download(int attachmentId)
    {
        TaskAttachment taskFile = null;

        if (attachmentId > 0)
        {
            // taskFile = <your code to get the file>
            // which assumes it's an object with relevant properties as required below

            if (taskFile != null)
            {
                var cd = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
                {
                    FileNameStar = taskFile.Filename
                };

                Response.Headers.Add(HeaderNames.ContentDisposition, cd.ToString());
            }
        }

        return new FileContentResult(taskFile?.FileData, taskFile?.FileContentType);
    }
}

Tiếp theo, đảm bảo khởi động ứng dụng của bạn (Startup.cs) được cấu hình để sử dụng chính xác MVC và có dòng sau đây (thêm nếu không):

        services.AddMvc();

.. và cuối cùng sửa đổi thành phần của bạn để liên kết với bộ điều khiển, ví dụ (ví dụ dựa trên lặp lại sử dụng một lớp tùy chỉnh):

    <tbody>
        @foreach (var attachment in yourAttachments)
        {
        <tr>
            <td><a href="api/FileHandling?attachmentId=@attachment.TaskAttachmentId" target="_blank">@attachment.Filename</a> </td>
            <td>@attachment.CreatedUser</td>
            <td>@attachment.Created?.ToString("dd MMM yyyy")</td>
            <td><ul><li class="oi oi-circle-x delete-attachment"></li></ul></td>
        </tr>
        }
        </tbody>

Hy vọng rằng điều này sẽ giúp bất cứ ai đấu tranh (như tôi!) Để có được câu trả lời thích hợp cho câu hỏi có vẻ đơn giản này trong vương quốc của Blazor khắc!


Mặc dù điều này có thể hữu ích cho những người khác gặp phải vấn đề tương tự như bạn đã làm, nhưng họ sẽ dễ khám phá hơn nếu bạn đăng câu hỏi của riêng bạn với một tiêu đề cho thấy nó là duy nhất với Blazor, tự trả lời và thêm nhận xét tại đây đề nghị mọi người tiếp cận câu hỏi này với các vấn đề liên quan đến blazor kiểm tra liên kết của bạn. Tôi cảm thấy mình đã đạt được ngay cả với câu hỏi ban đầu này là về ASP.NET MVC và điều chỉnh câu trả lời của nó có liên quan đến ASP.NET Core. Blazor là một con thú hoàn toàn khác, như bạn đã khám phá.
Nick Albrecht
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.