我在将存储在数据库中的文件发送回 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
}
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
由于“文档”变量上没有类型提示,我无法接受接受的答案: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);
}
AppDomain.CurrentDomain.BaseDirectory
的替代方案是 System.Web.HttpContext.Current.Server.MapPath("~")
,与本地计算机相比,这在真实服务器上可能会更好地工作。
查看文件(例如txt):
return File("~/TextFileInRootDir.txt", MediaTypeNames.Text.Plain);
下载文件(例如txt):
return File("~/TextFileInRootDir.txt", MediaTypeNames.Text.Plain, "TextFile.txt");
注意:要下载文件,我们应该传递 fileDownloadName 参数
Inline
属性,我可以将设置文件名的能力与是否强制下载的行为区分开来。
Darin Dimitrov's answer 是正确的。只是一个补充:
如果您的响应已包含“Content-Disposition”标头,Response.AppendHeader("Content-Disposition", cd.ToString());
可能会导致浏览器无法呈现文件。在这种情况下,您可能需要使用:
Response.Headers.Add("Content-Disposition", cd.ToString());
pdf
文件,如果我将内容类型设置为System.Net.Mime.MediaTypeNames.Application.Octet
,即使我设置了Inline = true
也会强制下载,但如果我设置为Response.ContentType = MimeMapping.GetMimeMapping(filePath)
,即application/pdf
,它可以正确打开而不是下载
Response.Headers.Add
需要 IIS 集成管道模式。另外,即使应用程序池设置为集成,它也会抛出异常。解决方案。使用 Response.AddHeader
。请参阅 SO 线程:stackoverflow.com/questions/22313167/…
我相信这个答案更干净,(基于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));
}
application/octet-stream
并且仍然导致文件被下载而不是显示,但它似乎是兼容的。
下面的代码对我有用,可以从 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);
}
Microsoft.AspNetCore.StaticFiles.FileExtensionContentTypeProvider
FileVirtualPath --> Research\Global Office Review.pdf
public virtual ActionResult GetFile()
{
return File(FileVirtualPath, "application/force-download", Path.GetFileName(FileVirtualPath));
}
Action 方法需要返回带有文件流、字节[] 或虚拟路径的 FileResult。您还需要知道正在下载的文件的内容类型。这是一个示例(快速/脏)实用程序方法。示例视频链接 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"},
...
};
}
}
如果像我一样,您在学习 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 领域为这个看似简单的问题获得适当答案的人......!
Inline = true
,请确保不要使用将文件名作为第三个参数的File()
的 3 参数重载。它将在 IE 中工作,但 Chrome 会报告重复的标题并拒绝显示图像。