12

We have some files stored in sql database. On an ASP.NET MVC3 form, we display 2 links:

View this file | Download this file

These links go to these corresponding action methods. The download works as expected -- clicking a link forces a save dialog in the browser. However, the display is causing duplicate Content-Disposition headers to be sent to the browser, resulting in an error on Chrome, and an empty page in Firefox.

[ActionName("display-file")]
public virtual ActionResult DisplayFile (Guid fileId, string fileName)
{
    var file = _repos.GetFileInfo(fileId);
    if (file != null)
    {
        Response.AddHeader("Content-Disposition", 
            string.Format("inline; filename={0}", file.Name));
        return File(file.Content, file.MimeType, file.Name);
    }
}

[ActionName("download-file")]
public virtual ActionResult DownloadFile (Guid fileId, string fileName)
{
    var file = _repos.GetFileInfo(fileId);
    if (file != null)
    {
        return File(file.Content, file.MimeType, file.Name);
    }
}

Here are the 2 headers sent to the browser for the display action:

Content-Disposition: inline; filename=name-of-my-file.pdf
Content-Disposition: attachment; filename="name-of-my-file.pdf"

I tried changing my custom content-disposition header to wrap the file name in double quotes, but it still sent 2 headers to the browser. I also tried removing the Content-Disposition header before adding the custom one, but it appears the attachment header is being added after the FileContentResult is returned.

This code used to work. I ran a test just yesterday and noticed it is no longer working in Chrome or Firefox. This could be due to updates in the browsers. IE8 and Safari still open the file correctly.

Update

Thanks again Darin, you are correct. We actually used this approach because of another question you answered.

A little more info about how this was ultimately solved on our end, we have a custom route for the display file link:

context.MapRoute(null,
    "path/to/display-file-attachment/{fileId}/{fileName}",
    new
    {
        area = "AreaName",
        controller = "ControllerName",
        action = "DisplayFile",
    }
);

The hyperlink on the page passes the file name to the action method through the route parameter, so it is already part of the URL. Thus, we did not need to add a custom content-disposition header in order to make the file name match the system's when a user decided to download it (by clicking save icon in browser PDF viewer). So we just used this:

[ActionName("display-file")]
public virtual ActionResult DisplayFile (Guid fileId, string fileName)
{
    var file = _repos.GetFileInfo(fileId);
    if (file != null)
    {
        // no custom content-disposition header, and no 3rd fileName argument
        return File(file.Content, file.MimeType);
    }
}
Community
  • 1
  • 1
danludwig
  • 46,965
  • 25
  • 159
  • 237
  • Firefox and Chrome have indeed become stricter in their handling of Content-Disposition headers. – Julian Reschke Dec 23 '11 at 13:59
  • @JulianReschke, would you please elaborate on non-ASCII characters? We have not tested this yet with unicode characters in file names. – danludwig Dec 23 '11 at 14:10
  • for non-ASCII in filenames in C-D to "work" in all browsers, servers currently need to do User-Agent sniffing. See http://greenbytes.de/tech/tc2231/ and http://greenbytes.de/tech/webdav/rfc6266.html. I have my doubts that ASP.net gets this right, but I'd love to find out otherwise. – Julian Reschke Dec 24 '11 at 09:17

1 Answers1

29

When you use the overload File(byte[] contents, string mimeType, string fileName) a Content-Disposition header is automatically added to the response with attachment, so you don't need to add it a second time. For inline you could use the following overload File(byte[] contents, string mimeType) and manually add the Content-Disposition header:

[ActionName("display-file")]
public virtual ActionResult DisplayFile(Guid fileId)
{
    var file = _repos.GetFileInfo(fileId);
    var cd = new ContentDisposition
    {
        Inline = true,
        FileName = file.Name
    };
    Response.AddHeader("Content-Disposition", cd.ToString()); 
    return File(file.Content, file.MimeType);
}

[ActionName("download-file")]
public virtual ActionResult DownloadFile(Guid fileId)
{
    var file = _repos.GetFileInfo(fileId);
    return File(file.Content, file.MimeType, file.Name);
}
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • You beat me to it, I was about to post a similar answer. Omiting the 3rd fileName argument works. However, the content-disposition does not need to be added in our case. Simply `return File(file.Content, file.MimeType);` works, since the file name is derived from a custom route. – danludwig Dec 23 '11 at 13:50
  • Well, hopefully the framework does the right thing with respect to non-ASCII characters... – Julian Reschke Dec 23 '11 at 13:58
  • @JulianReschke, what non-ASCII characters? The framework does nothing about them. It's up to you. If you are talking about non-ASCII characters in file names, then yes, that's horrible PITA. But it is not something that you could hope the framework to help you. It's just something that's differently implemented by different browsers. In short it's something that should not be used :-) – Darin Dimitrov Dec 23 '11 at 14:13
  • Darin: well, it works the same in all current browsers except Safari. – Julian Reschke Dec 24 '11 at 09:18
  • The only downside is that some of my unit tests that check the filename are failing. Can't find a way to get my unit tests to check the headers and the FileDownloadName property is empty. – Mayo Feb 24 '12 at 16:06
  • I think I am so good at what I do until I see @DarinDimitrov everywhere! – Fabio Milheiro Feb 05 '14 at 16:39
  • Thanks for this answer, I've been looking and trying so many other suggestions before finding this one that actually worked. – Kris Jul 16 '15 at 15:45
  • Yes - Good explanation and good solution, the type of answer that makes stackoverflow what it is. – Terrance00 Mar 29 '17 at 08:42