我遇到了一个问题,将存储在数据库中的文件发送回ASP中的用户。净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};

有什么建议吗?


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
}

当前回答

如果你像我一样,在学习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的领域里得到这个看似简单的问题的适当答案…!

其他回答

Darin Dimitrov的答案是正确的。补充一点:

响应。AppendHeader(“附加”,cd.ToString ());可能会导致浏览器无法呈现文件,如果你的响应已经包含一个“Content-Disposition”头。在这种情况下,你可能想使用:

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

如果你像我一样,在学习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的领域里得到这个看似简单的问题的适当答案…!

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

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

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