返回文件以在 ASP.NET MVC 中查看/下载

发布于 2024-11-03 16:21:04 字数 2209 浏览 2 评论 0原文

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

如果我选择查看名为 SomeRandomFile.bak 的文件,并且浏览器没有关联的程序来打开此类文件,那么我对其默认下载行为没有任何问题。但是,如果我选择查看名为 SomeRandomFile.pdfSomeRandomFile.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};

有什么建议吗?


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

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
}

I'm encountering a problem sending files stored in a database back to the user in ASP.NET MVC. What I want is a view listing two links, one to view the file and let the mimetype sent to the browser determine how it should be handled, and the other to force a download.

If I choose to view a file called SomeRandomFile.bak and the browser doesn't have an associated program to open files of this type, then I have no problem with it defaulting to the download behavior. However, if I choose to view a file called SomeRandomFile.pdf or SomeRandomFile.jpg I want the file to simply open. But I also want to keep a download link off to the side so that I can force a download prompt regardless of the file type. Does this make sense?

I have tried FileStreamResult and it works for most files, its constructor doesn't accept a filename by default, so unknown files are assigned a file name based on the URL (which does not know the extension to give based on content type). If I force the file name by specifying it, I lose the ability for the browser to open the file directly and I get a download prompt. Has anyone else encountered this?

These are the examples of what I've tried so far.

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

Any suggestions?


UPDATE:
This questions seems to strike a chord with a lot of people, so I thought I'd post an update. The warning on the accepted answer below that was added by Oskar regarding international characters is completely valid, and I've hit it a few times due to using the ContentDisposition class. I've since updated my implementation to fix this. While the code below is from my most recent incarnation of this problem in an ASP.NET Core (Full Framework) app, it should work with minimal changes in an older MVC application as well since I'm using the System.Net.Http.Headers.ContentDispositionHeaderValue class.

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
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

神仙妹妹 2024-11-10 16:21:04
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

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

NOTE: This example code above fails to properly account for international characters in the filename. See RFC6266 for the relevant standardization. I believe recent versions of ASP.Net MVC's File() method and the ContentDispositionHeaderValue class properly accounts for this. - Oskar 2016-02-25

权谋诡计 2024-11-10 16:21:04

由于“文档”变量没有类型提示,我对接受的答案遇到了麻烦: 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);
}

I had trouble with the accepted answer due to no type hinting on the "document" variable: var document = ... So I'm posting what worked for me as an alternative in case anybody else is having trouble.

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);
}
很酷又爱笑 2024-11-10 16:21:04

查看文件(例如txt):

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

下载文件(例如txt):

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

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

To view file (txt for example):

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

To download file (txt for example):

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

note: to download file we should pass fileDownloadName argument

枫以 2024-11-10 16:21:04

达林·迪米特洛夫的答案是正确的。只是补充一下:

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

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

Darin Dimitrov's answer is correct. Just an addition:

Response.AppendHeader("Content-Disposition", cd.ToString()); may cause the browser to fail rendering the file if your response already contains a "Content-Disposition" header. In that case, you may want to use:

Response.Headers.Add("Content-Disposition", cd.ToString());
生生漫 2024-11-10 16:21:04

我相信这个答案更清晰,(基于
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));
    }

I believe this answer is cleaner, (based on
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));
    }
凉薄对峙 2024-11-10 16:21:04

下面的代码适用于我从 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);
    }

Below code worked for me for getting a pdf file from an API service and response it out to the browser - hope it helps;

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);
    }
明月夜 2024-11-10 16:21:04

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

Action method needs to return FileResult with either a stream, byte[], or virtual path of the file. You will also need to know the content-type of the file being downloaded. Here is a sample (quick/dirty) utility method. Sample video link
How to download files using 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"},
            ...
        };
    }
}
如果没有你 2024-11-10 16:21:04

文件虚拟路径 -->研究\全球办公室评论.pdf

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

FileVirtualPath --> Research\Global Office Review.pdf

public virtual ActionResult GetFile()
{
    return File(FileVirtualPath, "application/force-download", Path.GetFileName(FileVirtualPath));
}
又爬满兰若 2024-11-10 16:21:04

如果您像我一样,在学习 Blazor 时通过 Razor 组件接触到这个主题,那么您会发现需要跳出更多思维来解决这个问题。如果(也像我一样)Blazor 是您第一次涉足 MVC 类型的世界,那么这有点像雷区,因为文档对于此类“琐碎”任务没有那么有帮助。

因此,在撰写本文时,如果不嵌入 MVC 控制器来处理文件下载部分,则无法使用普通 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/[email protected]" 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 领域获得这个看似简单的问题的适当答案......!

If, like me, you've come to this topic via Razor components as you're learning Blazor, then you'll find you need to think a little more outside of the box to solve this problem. It's a bit of a minefield if (also like me) Blazor is your first forray into the MVC-type world, as the documentation isn't as helpful for such 'menial' tasks.

So, at the time of writing, you cannot achieve this using vanilla Blazor/Razor without embedding an MVC controller to handle the file download part an example of which is as below:

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

Next, make sure your application startup (Startup.cs) is configured to correctly use MVC and has the following line present (add it if not):

        services.AddMvc();

.. and then finally modify your component to link to the controller, for example (iterative based example using a custom class):

    <tbody>
        @foreach (var attachment in yourAttachments)
        {
        <tr>
            <td><a href="api/[email protected]" 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>

Hopefully this helps anyone who struggled (like me!) to get an appropriate answer to this seemingly simple question in the realms of Blazor…!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文