2010-09-16 201 views
39

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

任何帮助,非常感谢!谢谢!

+0

看起来是[this]的一个副本(http://stackoverflow.com/q/3206682/1178314),但问得好。 – 2016-01-25 09:46:18

回答

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

这会返回重复的Content-Disposition标头,Chrome会拒绝该文件。有没有办法使用File方法,但没有重复头文件返回内联文件? – wilk 2013-11-12 16:45:04

+12

@wilk,不要将文件名保存在File(...) – user2320724 2014-02-19 18:45:35

+2

的调用中想我会添加 - 强制下载开关“inline;”成为“依恋”。 – Paul 2014-10-13 21:56:30

17

在HTTP级别上,您的“Content-Disposition”标题应该具有“内联”而不是“附件”。 不幸的是,这并不直接支持FileResult(或它的派生类)。

如果您已经在页面或处理程序中生成了文档,您可以简单地将浏览器重定向到那里。如果这不是你想要的,你可以对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); 
     } 
    }

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

+0

我已经去了ContentDisposition helper类的方法,只是为了实现MVC内部也使用它,但有一些正确处理utf-8文件名的黑客。 ContentDisposition助手类在编码utf-8值时会出错。有关更多详情,请参阅[我的评论](/ questions/1012437/uses-of-content-disposition-in-an-http-response-header/22221217#comment57484455_22221217)。 – 2016-01-25 09:33:25

0

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

它的工作原理很喜欢Marnix answer,但不是与ContentDisposition类,不幸的是不遵循RFC当文件名必须是UTF-8编码完全产生的头,它的调整,而不是通过MVC,所产生的报头符合RFC。

(本来我写了部分使用this response to another questionanother 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()); 
     } 
    } 
} 

使用范例:

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文件名不会与互联网工作资源管理器(包括11款,包括Windows 10 Edge,已经通过一些pdf文件测试),同时适用于Firefox和Chrome。 Internet Explorer将忽略文件名称。对于Internet Explorer,你需要破解你的url路径,这是非常糟糕的imo。请参阅this answer

0

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

窗口,然后Application Tab更改Portable Document FormatPreview in Firefox

相关问题