使用FileResult在Asp.Net MVC中下载任何类型的文件吗?


228

我建议我使用FileResult允许用户从我的Asp.Net MVC应用程序下载文件。但是我能找到的唯一示例始终与图像文件有关(指定内容类型为image / jpeg)。

但是,如果我不知道文件类型怎么办?我希望用户能够从我网站的文件区域下载几乎所有文件。

我已经读过一种执行此操作的方法(有关代码,请参见上一篇文章),除了一件事外,它实际上可以正常工作:另存为对话框中出现的文件名是通过文件路径与下划线连接的( folder_folder_file.ext)。另外,似乎人们认为我应该返回FileResult而不是使用我发现BinaryContentResult的自定义类。

有人知道在MVC中进行这种下载的“正确”方法吗?

编辑:我得到了答案(如下),但只是认为如果有人有兴趣,我应该发布完整的工作代码:

public ActionResult Download(string filePath, string fileName)
{
    string fullName = Path.Combine(GetBaseDir(), filePath, fileName);

    byte[] fileBytes = GetFile(fullName);
    return File(
        fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
}

byte[] GetFile(string s)
{
    System.IO.FileStream fs = System.IO.File.OpenRead(s);
    byte[] data = new byte[fs.Length];
    int br = fs.Read(data, 0, data.Length);
    if (br != fs.Length)
        throw new System.IO.IOException(s);
    return data;
}

12
您正在做的事情很危险。您几乎可以允许用户从服务器上下载执行用户可以访问的任何文件。
保罗·弗莱明

1
正确-删除文件路径,并将其钉在actionresult的正文中会更安全。至少这样,他们只能访问特定的文件夹。
shubniggurath

2
是否有任何工具可以让您发现诸如此类的潜在危险漏洞?
大卫

我发现Response.ContentType = MimeMapping.GetMimeMapping(filePath);stackoverflow.com/a/22231074/4573839
yu yang Jian

您在客户端使用什么?
FrenkyB

Answers:


425

您可以只指定通用的八位字节流MIME类型:

public FileResult Download()
{
    byte[] fileBytes = System.IO.File.ReadAllBytes(@"c:\folder\myfile.ext");
    string fileName = "myfile.ext";
    return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
}

4
好的,我可以尝试一下,但是byte []数组有什么用呢?
安德斯2010年

3
没关系,我想我已经知道了。我将文件名(完整路径)读入FileStream,然后读入字节数组,然后它就像一个符咒一样起作用!谢谢!
安德斯(Anders)2010年

5
这会将整个文件加载到内存中只是为了将其流式传输出去。对于大文件,这是猪。更好的解决方案是下面的解决方案,它不必首先将文件加载到内存中。
HBlackorby 2015年

13
是的,因为这个答案已经有将近五年的历史了。如果这样做是为了提供非常大的文件,请不要这样做。如果可能的话,请使用单独的静态文件服务器,以免占用应用程序线程,也不用使用许多新技术来服务自2010年以来添加到MVC的文件。这仅在MIME类型未知时显示要使用的正确MIME类型。 。ReadAllBytes是几年后在编辑中添加的。为什么这是我的第二个最强烈的回答?那好吧。
伊恩·亨利

10
得到这个错误:non-invocable member "File" cannot be used like a method.
A-Sharabiani

105

MVC框架本身支持此功能。所述System.Web.MVC.Controller.File控制器提供的方法通过返回一个文件 / / 阵列

例如,使用文件的虚拟路径,您可以执行以下操作。

return File(virtualFilePath, System.Net.Mime.MediaTypeNames.Application.Octet,  Path.GetFileName(virtualFilePath));

36

如果使用的是.NET Framework 4.5,则可以使用MimeMapping.GetMimeMapping(字符串FileName)来获取文件的MIME类型。这就是我在动作中使用它的方式。

return File(Path.Combine(@"c:\path", fileFromDB.FileNameOnDisk), MimeMapping.GetMimeMapping(fileFromDB.FileName), fileFromDB.FileName);

get Mime映射很不错,但是要弄清楚运行时文件的类型是不是一个麻烦的过程?
Mohammed Noureldin

@MohammedNoureldin不是“显示”它,而是基于文件扩展名或类似内容的简单映射表。服务器对所有静态文件都执行此操作,这并不慢。
Al Kepp '19

13

菲尔·哈克的文章不错创建了一个“自定义文件下载操作结果”类。您只需要指定文件的虚拟路径和要另存为的名称即可。

我使用了一次,这是我的代码。

        [AcceptVerbs(HttpVerbs.Get)]
        public ActionResult Download(int fileID)
        {
            Data.LinqToSql.File file = _fileService.GetByID(fileID);

            return new DownloadResult { VirtualPath = GetVirtualPath(file.Path),
                                        FileDownloadName = file.Name };
        }

在我的示例中,我存储了文件的物理路径,因此我使用了这种帮助器方法(我发现我不记得的地方)将其转换为虚拟路径

        private string GetVirtualPath(string physicalPath)
        {
            string rootpath = Server.MapPath("~/");

            physicalPath = physicalPath.Replace(rootpath, "");
            physicalPath = physicalPath.Replace("\\", "/");

            return "~/" + physicalPath;
        }

这是Phill Haack的文章中的全部课程

public class DownloadResult : ActionResult {

    public DownloadResult() {}

    public DownloadResult(string virtualPath) {
        this.VirtualPath = virtualPath;
    }

    public string VirtualPath {
        get;
        set;
    }

    public string FileDownloadName {
        get;
        set;
    }

    public override void ExecuteResult(ControllerContext context) {
        if (!String.IsNullOrEmpty(FileDownloadName)) {
            context.HttpContext.Response.AddHeader("content-disposition", 
            "attachment; filename=" + this.FileDownloadName)
        }

        string filePath = context.HttpContext.Server.MapPath(this.VirtualPath);
        context.HttpContext.Response.TransmitFile(filePath);
    }
}

1
是的,是的,我也看到了该文章,但它似乎与我所用的文章有点类似(请参阅上一篇文章的参考),他在页面顶部说,自己应该解决此问题。不再需要,因为:“新更新:不再需要此自定义ActionResult,因为ASP.NET MVC现在在框中包含一个。” 但是不幸的是,他没有说其他有关如何使用它的事情。
安德斯

@ManafAbuRous,如果您仔细阅读代码,您会发现它实际上将虚拟路径转换为物理路径(Server.MapPath(this.VirtualPath)),因此直接使用它而不进行更改是有点天真。您应该产生一个接受PhysicalPath给定的最终需求和所存储的内容。如果您假设物理路径和相对路径是相同的(不包括根),那将更加安全。经常存储的数据文件是App_Data。这不是相对路径。
保罗·弗莱明

GetVirtualPath很棒。。非常有用。谢谢!
Zvi Redler

6

感谢Ian Henry

如果您需要从MS SQL Server获取文件,这里是解决方案。

public FileResult DownloadDocument(string id)
        {
            if (!string.IsNullOrEmpty(id))
            {
                try
                {
                    var fileId = Guid.Parse(id);

                    var myFile = AppModel.MyFiles.SingleOrDefault(x => x.Id == fileId);

                    if (myFile != null)
                    {
                        byte[] fileBytes = myFile.FileData;
                        return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, myFile.FileName);
                    }
                }
                catch
                {
                }
            }

            return null;
        }

其中AppModelEntityFramework模型,而MyFiles在数据库中显示的FileDatavarbinary(MAX)MYFILES表。


2

它很简单,只需在directoryPath中给您的物理路径加上文件名

public FilePathResult GetFileFromDisk(string fileName)
{
    return File(directoryPath, "multipart/form-data", fileName);
}

客户端调用此方法呢?假设您要显示另存为对话框吗?
FrenkyB

0
   public ActionResult Download()
        {
            var document = //Obtain document from database context
    var cd = new System.Net.Mime.ContentDisposition
    {
        FileName = document.FileName,
        Inline = false,
    };
            Response.AppendHeader("Content-Disposition", cd.ToString());
            return File(document.Data, document.ContentType);
        }

-1

如果(string.IsNullOrWhiteSpace(fileName))返回Content(“不存在文件名”);

        var path = Path.Combine(your path, your filename);

        var stream = new FileStream(path, FileMode.Open);

        return File(stream, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);

-4

GetFile应该关闭文件(或在using中打开文件)。然后,您可以在转换为字节后删除文件-下载将在该字节缓冲区中完成。

    byte[] GetFile(string s)
    {
        byte[] data;
        using (System.IO.FileStream fs = System.IO.File.OpenRead(s))
        {
            data = new byte[fs.Length];
            int br = fs.Read(data, 0, data.Length);
            if (br != fs.Length)
                throw new System.IO.IOException(s);
        }
        return data;
    }

因此,在您的下载方法中...

        byte[] fileBytes = GetFile(file);
        // delete the file after conversion to bytes
        System.IO.File.Delete(file);
        // have the file download dialog only display the base name of the file            return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, Path.GetFileName(file));

2
请永远不要像这样将整个文件加载到内存中以进行生产
makhdumi
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.