将文件返回到ASP.NET MVC中的“查看/下载”


304

我在将数据库中存储的文件发送回ASP.NET MVC中的用户时遇到问题。我想要的是一个列出两个链接的视图,一个链接用于查看文件,并让发送给浏览器的mimetype确定应如何处理它,另一个链接用于强制下载。

如果我选择查看一个名为的文件SomeRandomFile.bak,而浏览器没有用于打开此类型文件的关联程序,则默认为下载行为时,我没有任何问题。但是,如果我选择查看一个名为的文件,SomeRandomFile.pdf或者SomeRandomFile.jpg我只想打开该文件。但我也想保留下载链接到一边,以便无论文件类型如何都可以强制执行下载提示。这有意义吗?

我已经尝试过了FileStreamResult,它适用于大多数文件,它的构造函数默认情况下不接受文件名,因此根据URL为未知文件分配了一个文件名(该文件名不知道基于内容类型的扩展名)。如果通过指定来强制使用文件名,那么浏览器将无法直接打开文件,并且会出现下载提示。有人遇到过这种情况么?

这些是到目前为止我尝试过的例子。

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

有什么建议么?


更新: 这个问题似乎引起了很多人的共鸣,所以我认为我应该发布更新。奥斯卡(Oskar)在以下接受的答案中添加了有关国际字符的警告,该警告是完全有效的,由于使用了ContentDisposition该类,我已经打了几次警告。此后,我更新了实现以解决此问题。尽管下面的代码来自我最近在ASP.NET Core(完整框架)应用程序中对该问题的化身,但由于我使用的是System.Net.Http.Headers.ContentDispositionHeaderValue类,因此它在较旧的MVC应用程序中也应进行最小的更改。

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
}

Answers:


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

注意:上面的此示例代码无法正确考虑文件名中的国际字符。有关相关标准化,请参见RFC6266。我相信ASP.Net MVC的File()方法和ContentDispositionHeaderValue类的最新版本可以正确地解决此问题。-奥斯卡2016-02-25


7
如果我没记错的话,只要文件名没有空格(可以通过HttpUtility.UrlEncode()来实现它),它就可以取消引用。
基思·威廉姆斯

22
注意:如果您使用此选项并进行设置,请Inline = true确保不要使用File()文件名作为第三个参数的3-param重载。它可以在IE中运行,但Chrome会报告重复的标题并拒绝提供图片。
浮士德节

74
var document = ...是什么类型?
TTT

7
@ user1103990,这是您的域模型。
Darin Dimitrov

3
使用MVC 5,不再需要content-disposition标头,因为它已经是响应标头的一部分。但是我只有FF的下载对话框,没有chrome和IE的对话框
Legends

124

由于在“ document”变量上没有任何类型提示,var document = ...因此我无法接受已接受的答案:因此,我发布了对我有用的方法,以防万一其他人遇到麻烦。

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
document变量只是一个类(POCO),代表有关您要返回的文档的信息。这也是接受的答案。它可能来自ORM,来自手动构建的SQL查询,来自文件系统(从中获取信息)或其他一些数据存储。原始问题与您的文档字节/文件名/ MIME类型来自何处无关,因此它被忽略以免混淆代码。不过,感谢您贡献了仅使用文件系统的示例。
Nick Albrecht

1
当文件名包含US-ASCII之外的国际字符时,此解决方案将无法正常工作。
Oskar Berggren

1
谢谢,拯救了我的一天:)
Dipesh

1
替代方法AppDomain.CurrentDomain.BaseDirectorySystem.Web.HttpContext.Current.Server.MapPath("~"),与本地计算机相比,它在真实服务器上可能会更好。
克里斯·汤普森

15

达林·迪米特洛夫(Darin Dimitrov)的答案是正确的。只是一个补充:

Response.AppendHeader("Content-Disposition", cd.ToString());如果您的响应已经包含“ Content-Disposition”标头,则可能导致浏览器无法呈现文件。在这种情况下,您可能要使用:

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

我发现content-type也会对pdf文件产生影响,如果将content-type设置为System.Net.Mime.MediaTypeNames.Application.Octet,即使设置也会强制下载Inline = true,但是如果将设置为Response.ContentType = MimeMapping.GetMimeMapping(filePath),则application/pdf它可以正确打开而不是下载
yu杨健

Response.Headers.Add需要IIS集成管道模式。另外,即使将应用程序池设置为集成,它也会引发异常。解。使用Response.AddHeader。参见SO线程:stackoverflow.com/questions/22313167/…–
罗兰

12

查看文件(例如txt):

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

下载文件(例如txt):

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

注意:要下载文件,我们应该传递fileDownloadName参数


这种方法的唯一问题是,仅在使用强制下载的方法时才提供文件名。当我要让浏览器确定如何打开文件时,它不允许我发送正确的文件名。因此,外部应用程序将尝试使用我的URL作为文件名。通常是数据库中文档的某种ID,通常没有文件扩展名。这使得名称很不匹配,因为名称不匹配用于访问文件的URL,因为如果您不提供该名称,则会出现这种情况。
尼克·阿尔布雷希特

通过使用InlineContent-Disposition上的属性,我可以将设置文件名的能力与是否强制下载分开。
尼克·阿尔布雷希特

4

我相信这个答案更干净,(基于 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
不推荐使用Content-Disposition,因为它是清晰和兼容的首选方法。stackoverflow.com/questions/10615797/...
尼克·阿尔布雷希特

感谢那。尽管我将MIME类型更改为application/octet-stream,但仍然导致文件下载而不是显示,并且似乎兼容。
Serj Sagan

1
说谎的内容类型听起来是一个非常糟糕的主意。一些浏览器依靠正确的内容类型在“保存或打开”对话框中为用户建议应用程序。
Oskar Berggren

如果您的目的是让浏览器建议一个应用程序,那很好,但是这个问题专门用于强制下载...
Serj Sagan

@SerjSagan我认为这更多是为了规避浏览器的行为,即默认情况下直接查看某些文件类型或使用插件,而不是在保存/打开之间进行选择。现在还没有尝试使用JPEG,因此不确定确切的行为。
Oskar Berggren

3

FileVirtualPath->研究\全球办公室评论.pdf

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

5
在上一个答案中已经提到了这一点,因此不建议这样做。有关原因的更多详细信息,请参见以下问题。stackoverflow.com/questions/10615797/…–
尼克·阿尔布雷希特

1
这样,它不会通过将文件加载到服务器上的内存中来使用服务器上的资源,对吗?
伊恩·乔维特

1
我相信是这样,因为您不需要r @CrashOverride
Bishoy Hanna

1

下面的代码对我有用,可以从API服务获取pdf文件并将其响应给浏览器-希望对您有所帮助;

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
感谢您的回答。您错过了我正在寻找的一种解决方案,其中包括可以指定文件名的部分。您只是返回字节,因此将从URL推断名称。我以PDF为例,但是我也需要它同时适用于其他多种文件类型。我应该提到,只要您使用.NET Framework 4.x和MVC <= 5,您的解决方案就可以正常工作。如果您是针对.NET Core运行的话,最好的选择是使用Microsoft.AspNetCore.StaticFiles.FileExtensionContentTypeProvider
Nick Albrecht

@NickAlbrecht我没有使用.Net Core-上面的示例用于浏览器上的pdf响应。但是,如果要下载,请尝试:File(fileContentBytes,System.Net.Mime.MediaTypeNames.Application.Pdf,“您的文件名”)。我不确定是否能找到答案。请告诉我是否有帮助。尽管如此,感谢.Net Core的建议。另外,如果您发现我的答案有用,请增加投票。
强尼男孩

很久以前,我已经用我标记为接受的答案解决了该问题。我还指出了一些警告,以防您发现它们有用。我最初的问题是2011年,所以现在已经过时了。
尼克·阿尔布雷希特'18

@NickAlbrecht谢谢。我不知道您解决了这个问题,但确实看到这是一个很旧的帖子。我在寻找一些与异步相关的答案时找到了这个论坛,我是异步过程的新手。我能够解决我的问题,所以只是分享。比您的时间还多。
强尼男孩

0

Action方法需要返回带有文件流,字节[]或虚拟路径的FileResult。您还需要知道要下载的文件的内容类型。这是一个示例(快速/脏)实用程序方法。示例视频链接 如何使用asp.net Core下载文件

[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"},
            ...
        };
    }
}

不泄露属于您的东西(例如库,工具,产品,教程或网站)的情况下,不公开属于您的东西就被视为Stack Overflow上的垃圾邮件。请参阅:什么表示“良好”的自我提升?自我推销一些提示和建议什么是“垃圾邮件”的堆栈溢出的确切定义是什么?以及导致垃圾邮件的原因
塞缪尔刘氏

0

如果像我一样,在学习Blazor时通过Razor组件进入了这个主题,那么您将发现需要更多地思考才能解决此问题。如果Blazor是您第一次涉足MVC类型的世界,那将是一个雷区,因为文档对于这种“艰巨的”任务没有帮助。

因此,在撰写本文时,如果未嵌入MVC控制器来处理文件下载部分,则无法使用Vanilla Blazor / Razor实现此目的,其示例如下:

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

接下来,确保将应用程序启动(Startup.cs)配置为正确使用MVC并显示以下行(如果没有,请添加):

        services.AddMvc();

..然后最后修改您的组件以链接到控制器,例如(使用自定义类的基于迭代的示例):

    <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>

希望这可以帮助任何挣扎(像我一样!)的人在Blazor领域中对这个看似简单的问题找到适当的答案……!


虽然这对于遇到与您相同的问题的其他人可能很有用,但如果您发布自己的问题并带有标题(表明标题是Blazor独有的标题),您可以自己回答,然后在此处添加评论以建议任何人达到此目的,对他们来说更容易发现这个问题有关blazor的问题,请查看您的链接。我觉得即使最初的问题是针对ASP.NET MVC的,我也能解决这个问题,并将其答案调整为与ASP.NET Core有关。正如您所发现的,Blazor是完全不同的野兽。
尼克·阿尔布雷希特
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.