63

Is there a way to stream a file using ASP.NET MVC FileContentResult within the browser with a specific name?

I have noticed that you can either have a FileDialog (Open/Save) or you can stream the file in a browser window, but then it will use the ActionName when you try to save the file.

I have the following scenario:

byte[] contents = DocumentServiceInstance.CreateDocument(orderId, EPrintTypes.Quote);
result = File(contents, "application/pdf", String.Format("Quote{0}.pdf", orderId));

When I use this, I can stream the bytes, but a OPEN/SAVE file dialog is given to the user. I would like to actually stream this file in a browser window.

If I just use the FilePathResult, it shows the file in a browser window, but then when I click on "Save" button to save the file in PDF, it shows me the Action Name as the name of the file.

Has anyone encountered this?

GEOCHET
  • 20,623
  • 15
  • 71
  • 98
Anup Marwadi
  • 2,245
  • 4
  • 23
  • 39

6 Answers6

94
public ActionResult Index()
{
    byte[] contents = FetchPdfBytes();
    return File(contents, "application/pdf", "test.pdf");
}

and for opening the PDF inside the browser you will need to set the Content-Disposition header:

public ActionResult Index()
{
    byte[] contents = FetchPdfBytes();
    Response.AddHeader("Content-Disposition", "inline; filename=test.pdf");
    return File(contents, "application/pdf");
}
Darin Dimitrov
  • 960,118
  • 257
  • 3,196
  • 2,876
  • Hello Darin, This opens up an OPEN/SAVE Dialog like I mentioned. I want this to open a file inside the browser. – Anup Marwadi Jul 09 '10 at 00:55
  • can we have a generic value instead of "application/pdf" ? like I am not sure the type of the file. it's the user who uploaded it i the first place – neebz Jun 20 '11 at 23:04
  • 3
    We used this approach, and it causes MVC3 to send 2 Content-Disposition headers to the browser, which causes Chrome and Firefox to not display the file. http://stackoverflow.com/q/8616691/304832 – danludwig Dec 23 '11 at 13:37
  • @Darin: If I am calling this controller action through AJAX call, then what should be the dataType and contentType in AJAX? Because I am not getting any file open box on callin gthis action. My pdf bytes are getting genearated well. – Anil Soman Aug 24 '12 at 07:42
  • 2
    @AnilSoman, it makes no sense to call a controller action that returns a file with AJAX. You will never get any file open box if you use AJAX. – Darin Dimitrov Aug 24 '12 at 07:44
  • @Darin: Yes..but my user clicks on save image icon to get the result in pdf. Hence decided to call ajax. – Anil Soman Aug 24 '12 at 07:45
  • 2
    @AnilSoman, sorry that's impossible with AJAX. You could use a normal image button that submits the form without any AJAX call. – Darin Dimitrov Aug 24 '12 at 07:45
  • @Darin: ya.. seems I need to change the approach as you said. Thanks anyway! – Anil Soman Aug 24 '12 at 07:48
  • I think this should work for You: use Darin's code on the server and run this on a client: window.location.href = '@Url.Action("ShowPdf")'; //or some other name of the action You are using – Tom Dec 24 '13 at 09:21
  • none of these answers show how to stream a file.they show how to load entire file into memory and return it. This is unnecessary and will throw OutOfMemoryException on large files. – Sonic Soul Aug 02 '16 at 15:17
  • I was able to get it working via a stream by returning File(stream, "application/pdf"). Basically I left off the third argument, the file name, and then made sure my URL had the filename in it so the browser knows what name to use if the user wants to save the file. Otherwise it will try to use whatever it can from the URL so they get the id like "1234.pdf" but if you make your url "download/1234/Whatever.pdf" then the browser will use that name instead. Except for the original version of Edge, it still can't figure out the name based on the URL but the Chromium based version can. – Steve Hiner Nov 11 '19 at 22:38
58

Actually, the absolutely easiest way is to do the following...

byte[] content = your_byte[];

FileContentResult result = new FileContentResult(content, "application/octet-stream") 
{
  FileDownloadName = "your_file_name"
};

return result;
azarc3
  • 1,058
  • 12
  • 19
13

This might be helpful for whoever else faces this problem. I finally figured out a solution. Turns out, even if we use the inline for "content-disposition" and specify a file name, the browsers still do not use the file name. Instead browsers try and interpret the file name based on the Path/URL.

You can read further on this URL: Securly download file inside browser with correct filename

This gave me an idea, I just created my URL route that would convert the URL and end it with the name of the file I wanted to give the file. So for e.g. my original controller call just consisted of passing the Order Id of the Order being printed. I was expecting the file name to be of the format Order{0}.pdf where {0} is the Order Id. Similarly for quotes, I wanted Quote{0}.pdf.

In my controller, I just went ahead and added an additional parameter to accept the file name. I passed the filename as a parameter in the URL.Action method.

I then created a new route that would map that URL to the format: http://localhost/ShoppingCart/PrintQuote/1054/Quote1054.pdf


routes.MapRoute("", "{controller}/{action}/{orderId}/{fileName}",
                new { controller = "ShoppingCart", action = "PrintQuote" }
                , new string[] { "x.x.x.Controllers" }
            );

This pretty much solved my issue. Hoping this helps someone!

Cheerz, Anup

Community
  • 1
  • 1
Anup Marwadi
  • 2,245
  • 4
  • 23
  • 39
  • A hack, but a very effective hack! Thanks! – J.T. Taylor Oct 15 '13 at 01:17
  • Unfortunately, still needed with IE 11. Not needed with Chrome and Firefox. – Frédéric Jan 25 '16 at 09:47
  • Brillant worked for me... but one gotcha I discovered, ensure that any static file handlers don't intercept the request before it gets to managed code, that was my additional hurdle. i.e by default requests for anything *.jpg for instance don't go anywhere near the managed code, so routes are not even checked, additional [here](https://stackoverflow.com/questions/19483614/mvc-ignoring-jpg-file-extension-on-certain-route-use-controller-action) – OJay Feb 14 '19 at 01:40
8

Previous answers are correct: adding the line...

Response.AddHeader("Content-Disposition", "inline; filename=[filename]");

...will causing multiple Content-Disposition headers to be sent down to the browser. This happens b/c FileContentResult internally applies the header if you supply it with a file name. An alternative, and pretty simple, solution is to simply create a subclass of FileContentResult and override its ExecuteResult() method. Here's an example that instantiates an instance of the System.Net.Mime.ContentDisposition class (the same object used in the internal FileContentResult implementation) and passes it into the new class:

public class FileContentResultWithContentDisposition : FileContentResult
{
    private const string ContentDispositionHeaderName = "Content-Disposition";

    public FileContentResultWithContentDisposition(byte[] fileContents, string contentType, ContentDisposition contentDisposition)
        : base(fileContents, contentType)
    {
        // check for null or invalid ctor arguments
        ContentDisposition = contentDisposition;
    }

    public ContentDisposition ContentDisposition { get; private set; }

    public override void ExecuteResult(ControllerContext context)
    {
        // check for null or invalid method argument
        ContentDisposition.FileName = ContentDisposition.FileName ?? FileDownloadName;
        var response = context.HttpContext.Response;
        response.ContentType = ContentType;
        response.AddHeader(ContentDispositionHeaderName, ContentDisposition.ToString());
        WriteFile(response);
    }
}

In your Controller, or in a base Controller, you can write a simple helper to instantiate a FileContentResultWithContentDisposition and then call it from your action method, like so:

protected virtual FileContentResult File(byte[] fileContents, string contentType, ContentDisposition contentDisposition)
{
    var result = new FileContentResultWithContentDisposition(fileContents, contentType, contentDisposition);
    return result;
}

public ActionResult Report()
{
    // get a reference to your document or file
    // in this example the report exposes properties for
    // the byte[] data and content-type of the document
    var report = ...
    return File(report.Data, report.ContentType, new ContentDisposition {
        Inline = true,
        FileName = report.FileName
    });
}

Now the file will be sent to the browser with the file name you choose and with a content-disposition header of "inline; filename=[filename]".

I hope that helps!

Kevin Babcock
  • 9,828
  • 18
  • 66
  • 88
  • I have gone the `ContentDisposition` helper class way first, just to realize MVC was using it internally too, but with some hack for correctly handling utf-8 file name. `ContentDisposition` helper class does it wrong when it has to encode utf-8 values. For more details, see [my comment here](/questions/1012437/uses-of-content-disposition-in-an-http-response-header/22221217#comment57484455_22221217). – Frédéric Jan 25 '16 at 09:21
  • ExecuteResult is not overridable, what to do? – Nithin Paul Jan 06 '21 at 07:19
6

The absolute easiest way to stream a file into browser using ASP.NET MVC is this:

public ActionResult DownloadFile() {
    return File(@"c:\path\to\somefile.pdf", "application/pdf", "Your Filename.pdf");
}

This is easier than the method suggested by @azarc3 since you don't even need to read the bytes.

Credit goes to: http://prideparrot.com/blog/archive/2012/8/uploading_and_returning_files#how_to_return_a_file_as_response

** Edit **

Apparently my 'answer' is the same as the OP's question. But I am not facing the problem he is having. Probably this was an issue with older version of ASP.NET MVC?

Rosdi Kasim
  • 20,195
  • 22
  • 115
  • 142
  • Its issue can be abstracted to 'MVC send a `content-disposition` header with `attachment` disposition when specifying a file name, how to get it send it as `inline`? Test your solution response headers, you would normally see `attachment` too. – Frédéric Jan 25 '16 at 09:16
  • For me this does the trick. Didn't think it was that simple until Rosdi pointed it out – joseph.c Dec 20 '19 at 20:26
-1
public FileContentResult GetImage(int productId) { 
     Product prod = repository.Products.FirstOrDefault(p => p.ProductID == productId); 
     if (prod != null) { 
         return File(prod.ImageData, prod.ImageMimeType); 
      } else { 
         return null; 
     } 
}
plsgogame
  • 1,304
  • 14
  • 28