asp.net-mvc ASP.NET MVC:如何让浏览器打开并显示 PDF 而不是显示下载提示?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3724278/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
ASP.NET MVC: How can I get the browser to open and display a PDF instead of displaying a download prompt?
提问by Lucifer Sam
Ok, so I have an action method that generates a PDF and returns it to the browser. The problem is that instead of automatically opening the PDF, IE displays a download prompt even though it knows what kind of file it is. Chrome does the same thing. In both browsers if I click a link to a PDF file that is stored on a server it will open up just fine and never display a download prompt.
好的,所以我有一个生成 PDF 并将其返回给浏览器的操作方法。问题是 IE 不会自动打开 PDF,而是显示下载提示,即使它知道它是什么类型的文件。Chrome 也做同样的事情。在这两个浏览器中,如果我单击指向存储在服务器上的 PDF 文件的链接,它会打开得很好并且永远不会显示下载提示。
Here is the code that is called to return the PDF:
这是调用以返回 PDF 的代码:
public FileResult Report(int id)
{
var customer = customersRepository.GetCustomer(id);
if (customer != null)
{
return File(RenderPDF(this.ControllerContext, "~/Views/Forms/Report.aspx", customer), "application/pdf", "Report - Customer # " + id.ToString() + ".pdf");
}
return null;
}
Here's the response header from the server:
这是来自服务器的响应头:
HTTP/1.1 200 OK
Server: ASP.NET Development Server/10.0.0.0
Date: Thu, 16 Sep 2010 06:14:13 GMT
X-AspNet-Version: 4.0.30319
X-AspNetMvc-Version: 2.0
Content-Disposition: attachment; filename="Report - Customer # 60.pdf"
Cache-Control: private, s-maxage=0
Content-Type: application/pdf
Content-Length: 79244
Connection: Close
Do I have to add something special to the response to get the browser to open the PDF automatically?
我是否必须在响应中添加一些特殊的内容才能让浏览器自动打开 PDF?
Any help is greatly appreciated! Thanks!
任何帮助是极大的赞赏!谢谢!
回答by Darin Dimitrov
Response.AppendHeader("Content-Disposition", "inline; filename=foo.pdf");
return File(...
回答by Marnix van Valen
On the HTTP level your 'Content-Disposition' header should have 'inline' not 'attachment'. Unfortunately, that's not supported by the FileResult (or it's derived classes) directly.
在 HTTP 级别上,您的“Content-Disposition”标头应为“内联”而不是“附件”。不幸的是,FileResult(或其派生类)不直接支持。
If you're already generating the document in a page or handler you could simply redirect the browser there. If that's not what you want you could subclass the FileResult and add support for streaming documents inline.
如果您已经在页面或处理程序中生成文档,您可以简单地将浏览器重定向到那里。如果这不是您想要的,您可以子类化 FileResult 并添加对内联流文档的支持。
public class CustomFileResult : FileContentResult
{
public CustomFileResult( byte[] fileContents, string contentType ) : base( fileContents, contentType )
{
}
public bool Inline { get; set; }
public override void ExecuteResult( ControllerContext context )
{
if( context == null )
{
throw new ArgumentNullException( "context" );
}
HttpResponseBase response = context.HttpContext.Response;
response.ContentType = ContentType;
if( !string.IsNullOrEmpty( FileDownloadName ) )
{
string str = new ContentDisposition { FileName = this.FileDownloadName, Inline = Inline }.ToString();
context.HttpContext.Response.AddHeader( "Content-Disposition", str );
}
WriteFile( response );
}
}
A simpler solution is not to specify the filename on the Controller.File
method. This way you will not get the ContentDisposition header, which means you loose the file name hint when saving the PDF.
一个更简单的解决方案是不在方法上指定文件名Controller.File
。这样你就不会得到 ContentDisposition 标题,这意味着你在保存 PDF 时会丢失文件名提示。
回答by Frédéric
I use following classes for having more options with content-disposition header.
我使用以下类为内容处置标头提供更多选项。
It works quite like Marnix answer, but instead of fully generating the header with the ContentDisposition
class, which unfortunately does not comply to RFCwhen file name has to be utf-8 encoded, it tweaks instead the header generated by MVC, which complies to RFC.
它的工作原理与Marnix answer非常相似,但不是完全生成带有ContentDisposition
类的标头,不幸的是,当文件名必须是 utf-8 编码时,它不符合RFC,而是调整了 MVC 生成的标头,它符合 RFC。
(Originally, I have written that in part using this response to another questionand this another one.)
using System;
using System.IO;
using System.Web;
using System.Web.Mvc;
namespace Whatever
{
/// <summary>
/// Add to FilePathResult some properties for specifying file name without forcing a download and specifying size.
/// And add a workaround for allowing error cases to still display error page.
/// </summary>
public class FilePathResultEx : FilePathResult
{
/// <summary>
/// In case a file name has been supplied, control whether it should be opened inline or downloaded.
/// </summary>
/// <remarks>If <c>FileDownloadName</c> is <c>null</c> or empty, this property has no effect (due to current implementation).</remarks>
public bool Inline { get; set; }
/// <summary>
/// Whether file size should be indicated or not.
/// </summary>
/// <remarks>If <c>FileDownloadName</c> is <c>null</c> or empty, this property has no effect (due to current implementation).</remarks>
public bool IncludeSize { get; set; }
public FilePathResultEx(string fileName, string contentType) : base(fileName, contentType) { }
public override void ExecuteResult(ControllerContext context)
{
FileResultUtils.ExecuteResultWithHeadersRestoredOnFailure(context, base.ExecuteResult);
}
protected override void WriteFile(HttpResponseBase response)
{
if (Inline)
FileResultUtils.TweakDispositionAsInline(response);
// File.Exists is more robust than testing through FileInfo, especially in case of invalid path: it does yield false rather than an exception.
// We wish not to crash here, in order to let FilePathResult crash in its usual way.
if (IncludeSize && File.Exists(FileName))
{
var fileInfo = new FileInfo(FileName);
FileResultUtils.TweakDispositionSize(response, fileInfo.Length);
}
base.WriteFile(response);
}
}
/// <summary>
/// Add to FileStreamResult some properties for specifying file name without forcing a download and specifying size.
/// And add a workaround for allowing error cases to still display error page.
/// </summary>
public class FileStreamResultEx : FileStreamResult
{
/// <summary>
/// In case a file name has been supplied, control whether it should be opened inline or downloaded.
/// </summary>
/// <remarks>If <c>FileDownloadName</c> is <c>null</c> or empty, this property has no effect (due to current implementation).</remarks>
public bool Inline { get; set; }
/// <summary>
/// If greater than <c>0</c>, the content size to include in content-disposition header.
/// </summary>
/// <remarks>If <c>FileDownloadName</c> is <c>null</c> or empty, this property has no effect (due to current implementation).</remarks>
public long Size { get; set; }
public FileStreamResultEx(Stream fileStream, string contentType) : base(fileStream, contentType) { }
public override void ExecuteResult(ControllerContext context)
{
FileResultUtils.ExecuteResultWithHeadersRestoredOnFailure(context, base.ExecuteResult);
}
protected override void WriteFile(HttpResponseBase response)
{
if (Inline)
FileResultUtils.TweakDispositionAsInline(response);
FileResultUtils.TweakDispositionSize(response, Size);
base.WriteFile(response);
}
}
/// <summary>
/// Add to FileContentResult some properties for specifying file name without forcing a download and specifying size.
/// And add a workaround for allowing error cases to still display error page.
/// </summary>
public class FileContentResultEx : FileContentResult
{
/// <summary>
/// In case a file name has been supplied, control whether it should be opened inline or downloaded.
/// </summary>
/// <remarks>If <c>FileDownloadName</c> is <c>null</c> or empty, this property has no effect (due to current implementation).</remarks>
public bool Inline { get; set; }
/// <summary>
/// Whether file size should be indicated or not.
/// </summary>
/// <remarks>If <c>FileDownloadName</c> is <c>null</c> or empty, this property has no effect (due to current implementation).</remarks>
public bool IncludeSize { get; set; }
public FileContentResultEx(byte[] fileContents, string contentType) : base(fileContents, contentType) { }
public override void ExecuteResult(ControllerContext context)
{
FileResultUtils.ExecuteResultWithHeadersRestoredOnFailure(context, base.ExecuteResult);
}
protected override void WriteFile(HttpResponseBase response)
{
if (Inline)
FileResultUtils.TweakDispositionAsInline(response);
if (IncludeSize)
FileResultUtils.TweakDispositionSize(response, FileContents.LongLength);
base.WriteFile(response);
}
}
public static class FileResultUtils
{
public static void ExecuteResultWithHeadersRestoredOnFailure(ControllerContext context, Action<ControllerContext> executeResult)
{
if (context == null)
throw new ArgumentNullException("context");
if (executeResult == null)
throw new ArgumentNullException("executeResult");
var response = context.HttpContext.Response;
var previousContentType = response.ContentType;
try
{
executeResult(context);
}
catch
{
if (response.HeadersWritten)
throw;
// Error logic will usually output a content corresponding to original content type. Restore it if response can still be rewritten.
// (Error logic should ensure headers positionning itself indeed... But this is not the case at least with HandleErrorAttribute.)
response.ContentType = previousContentType;
// If a content-disposition header have been set (through DownloadFilename), it must be removed too.
response.Headers.Remove(ContentDispositionHeader);
throw;
}
}
private const string ContentDispositionHeader = "Content-Disposition";
// Unfortunately, the content disposition generation logic is hidden in an Mvc.Net internal class, while not trivial (UTF-8 support).
// Hacking it after its generation.
// Beware, do not try using System.Net.Mime.ContentDisposition instead, it does not conform to the RFC. It does some base64 UTF-8
// encoding while it should append '*' to parameter name and use RFC 5987 encoding. http://tools.ietf.org/html/rfc6266#section-4.3
// And https://stackoverflow.com/a/22221217/1178314 comment.
// To ask for a fix: https://github.com/aspnet/Mvc
// Other class : System.Net.Http.Headers.ContentDispositionHeaderValue looks better. But requires to detect if the filename needs encoding
// and if yes, use the 'Star' suffixed property along with setting the sanitized name in non Star property.
// MVC 6 relies on ASP.NET 5 https://github.com/aspnet/HttpAbstractions which provide a forked version of previous class, with a method
// for handling that: https://github.com/aspnet/HttpAbstractions/blob/dev/src/Microsoft.Net.Http.Headers/ContentDispositionHeaderValue.cs
// MVC 6 stil does not give control on FileResult content-disposition header.
public static void TweakDispositionAsInline(HttpResponseBase response)
{
var disposition = response.Headers[ContentDispositionHeader];
const string downloadModeToken = "attachment;";
if (string.IsNullOrEmpty(disposition) || !disposition.StartsWith(downloadModeToken, StringComparison.OrdinalIgnoreCase))
return;
response.Headers.Remove(ContentDispositionHeader);
response.Headers.Add(ContentDispositionHeader, "inline;" + disposition.Substring(downloadModeToken.Length));
}
public static void TweakDispositionSize(HttpResponseBase response, long size)
{
if (size <= 0)
return;
var disposition = response.Headers[ContentDispositionHeader];
const string sizeToken = "size=";
// Due to current ancestor semantics (no file => inline, file name => download), handling lack of ancestor content-disposition
// is non trivial. In this case, the content is by default inline, while the Inline property is <c>false</c> by default.
// This could lead to an unexpected behavior change. So currently not handled.
if (string.IsNullOrEmpty(disposition) || disposition.Contains(sizeToken))
return;
response.Headers.Remove(ContentDispositionHeader);
response.Headers.Add(ContentDispositionHeader, disposition + "; " + sizeToken + size.ToString());
}
}
}
Sample usage:
示例用法:
public FileResult Download(int id)
{
// some code to get filepath and filename for browser
...
return
new FilePathResultEx(filepath, System.Web.MimeMapping.GetMimeMapping(filename))
{
FileDownloadName = filename,
Inline = true
};
}
Note that specifying a file name with Inline
will not work with Internet Explorer (11 included, Windows 10 Edge included, tested with some pdf files), while it works with Firefox and Chrome. Internet Explorer will ignore the file name. For Internet Explorer, you need to hack your url path, which is quite bad imo. See this answer.
请注意,指定文件名 withInline
不适用于 Internet Explorer(包括 11,包括 Windows 10 Edge,已使用某些 pdf 文件进行测试),而它适用于 Firefox 和 Chrome。Internet Explorer 将忽略文件名。对于 Internet Explorer,您需要破解您的 url 路径,这是非常糟糕的 imo。看到这个答案。
回答by sosha
I had same issue,but none of the solutions not worked in Firefoxuntil I changed the Options of my browser. In Options
我遇到了同样的问题,但是在我更改浏览器的选项之前,没有一个解决方案在Firefox 中不起作用。在Options
window,then Application Tab
change the Portable Document Format
to Preview in Firefox
.
窗口,然后 Application Tab
将 更改Portable Document Format
为Preview in Firefox
。