ASP.NET MVC:如何让浏览器打开并显示PDF,而不是显示下载提示?

好的,我有一个生成PDF并将其返回给浏览器的操作方法。 问题是,即使知道它是什么types的文件,IE也不会自动打开PDF,而是显示下载提示。 Chrome做同样的事情。 在这两种浏览器中,如果我点击一个链接到存储在服务器上的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; } 

这是来自服务器的响应标题:

 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 

我是否需要添加一些特殊的响应来让浏览器自动打开PDF?

任何帮助是极大的赞赏! 谢谢!

 Response.AppendHeader("Content-Disposition", "inline; filename=foo.pdf"); return File(... 

在HTTP层面上,你的“Content-Disposition”头应该有“内联”而不是“附件”。 不幸的是,这不被FileResult(或它的派生类)直接支持。

如果您已经在页面或处理程序中生成了文档,则可以简单地将浏览器redirect到那里。 如果这不是你想要的,你可以inheritanceFileResult的子类并添加内联stream文档的支持。

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

一个更简单的解决scheme是不要在Controller.File方法中指定文件名。 这样你就不会得到ContentDisposition头,这意味着你在保存PDF时会丢失文件名提示。

我使用下面的类来获得更多的内容处置头的选项。

它的工作原理与Marnix相似,但不是用ContentDisposition类完全生成头文件,当文件名必须是utf-8编码时,不幸的是不符合RFC ,而是调整了MVC生成的头,而MVC符合RFC 。

(原来,我已经写了部分使用这个回答的另一个问题 ,这是另一个问题 。)

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

示例用法:

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

请注意,使用Inline指定文件名将不适用于Internet Explorer(包括11,包括Windows 10 Edge,使用某些pdf文件进行testing),而它可以与Firefox和Chrome一起使用。 Internet Explorer将忽略文件名称。 对于Internet Explorer,你需要破解你的URLpath,这是非常糟糕的IMO。 看到这个答案 。

我有同样的问题,但没有任何解决scheme不工作在Firefox,直到我改变了我的浏览器的选项。 在Options

窗口,然后在“ Application Tab中将Portable Document Format更改为Preview in Firefox