240

I've had it suggested to me that I should use FileResult to allow users to download files from my Asp.Net MVC application. But the only examples of this I can find always has to do with image files (specifying content type image/jpeg).

But what if I can't know the file type? I want users to be able to download pretty much any file from the filearea of my site.

I had read one method of doing this (see a previous post for the code), that actually works fine, except for one thing: the name of the file that comes up in the Save As dialog is concatenated from the file path with underscores (folder_folder_file.ext). Also, it seems people think I should return a FileResult instead of using this custom class that I had found BinaryContentResult.

Anyone know the "correct" way of doing such a download in MVC?

EDIT: I got the answer (below), but just thought I should post the full working code if someone else is interested:

public ActionResult Download(string filePath, string fileName)
{
    string fullName = Path.Combine(GetBaseDir(), filePath, fileName);

    byte[] fileBytes = GetFile(fullName);
    return File(
        fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
}

byte[] GetFile(string s)
{
    System.IO.FileStream fs = System.IO.File.OpenRead(s);
    byte[] data = new byte[fs.Length];
    int br = fs.Read(data, 0, data.Length);
    if (br != fs.Length)
        throw new System.IO.IOException(s);
    return data;
}
Community
  • 1
  • 1
Anders
  • 12,086
  • 23
  • 97
  • 148
  • 12
    What you're doing is rather dangerous. You're pretty much allowing users to download any file from your server that the executing user can access. – Paul Fleming Sep 01 '13 at 11:18
  • 1
    True - removing the file path, and nailing it down in the body of the actionresult would be somewhat safer. At least that way they only have access to a certain folder. – shubniggurath Dec 30 '13 at 21:25
  • 2
    Are there any tools that allow you to find potentially dangerous loopholes such as this one? – David Sep 29 '15 at 08:34
  • I find that it's convenient to set content-type as `Response.ContentType = MimeMapping.GetMimeMapping(filePath);`, from https://stackoverflow.com/a/22231074/4573839 – yu yang Jian Oct 17 '18 at 05:32
  • What are you using on client side? – FrenkyB Apr 27 '19 at 03:38

9 Answers9

442

You can just specify the generic octet-stream MIME type:

public FileResult Download()
{
    byte[] fileBytes = System.IO.File.ReadAllBytes(@"c:\folder\myfile.ext");
    string fileName = "myfile.ext";
    return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
}
Tieson T.
  • 20,030
  • 4
  • 69
  • 86
Ian Henry
  • 21,297
  • 4
  • 47
  • 60
  • 4
    Ok, I could try that, but what goes into the byte[] array? – Anders Aug 31 '10 at 15:40
  • 3
    Never mind, I think I figured it out. I read the filename (full path) into a FileStream and then into a byte array, and then it worked like a charm! Thanks! – Anders Aug 31 '10 at 16:13
  • Awesome! just what I needed. +1 – GETah Sep 23 '14 at 17:52
  • Brilliant! The only addition I would suggest is to use IO.Path.GetFileName(path) to get the file name when not using a hardcoded file path. – apc Jan 07 '15 at 10:23
  • 5
    This loads the entire file into memory just to stream it out; for big files, this is a hog. A much better solution is the one below that doesn't have to load the file into memory first. – HBlackorby May 06 '15 at 20:42
  • 13
    As this answer is almost five years old, yeah. If you're doing this to serve very large files, don't. If possible, use a separate static file server so you don't tie up your application threads, or one of many new techniques for serving files added to MVC since 2010. This just shows the correct MIME type to use when the MIME type is unknown. `ReadAllBytes` was added years later in an edit. Why is this my second most upvoted answer? Oh well. – Ian Henry May 06 '15 at 21:51
  • 10
    Getting this error: `non-invocable member "File" cannot be used like a method.` – A-Sharabiani Feb 02 '16 at 22:42
  • What to return when File.Exists() returns false means file not exist but as per return type we have to return something? – Mayur Patel Mar 15 '16 at 08:10
  • @MayurPatel That sounds like a 404 to me. – Ian Henry Mar 15 '16 at 14:19
  • Yes but what should i return in that case if i return NULL it shows blank page in My MVC app – Mayur Patel Mar 16 '16 at 06:04
  • @MayurPatel SO comments aren't a great place for Q&A; you should consider posting a new question. – Ian Henry Mar 16 '16 at 13:15
  • 1
    @AliSharabiani I guess you figured it out, but I wanted to comment since it has happened to me to: You must reference `File` as `System.IO.File` otherwise the compiler thinks it's `this.File` which is a method within the `Controller` class. – tfrascaroli Sep 14 '16 at 06:52
  • @IanHenry, you could always edit your own answer to include the more modern, correct ways of doing this. That is usually preferred to leaving a comment on your own answer. – NH. Dec 14 '17 at 23:35
  • can I call this Download method directly from ajax?? Please Help me out. I m doing the same thing and calling this method in ajax so the file is returned successfully by my ajax call always go into error – rinku Choudhary Nov 05 '19 at 11:34
  • Do you have to handle the download to client using Javascript? –  Jul 02 '20 at 21:40
107

The MVC framework supports this natively. The System.Web.MVC.Controller.File controller provides methods to return a file by name/stream/array.

For example using a virtual path to the file you could do the following.

return File(virtualFilePath, System.Net.Mime.MediaTypeNames.Application.Octet,  Path.GetFileName(virtualFilePath));
Jonathan
  • 1,265
  • 1
  • 8
  • 12
38

If you're using .NET Framework 4.5 then you use use the MimeMapping.GetMimeMapping(string FileName) to get the MIME-Type for your file. This is how I've used it in my action.

return File(Path.Combine(@"c:\path", fileFromDB.FileNameOnDisk), MimeMapping.GetMimeMapping(fileFromDB.FileName), fileFromDB.FileName);
Salman Hasrat Khan
  • 1,859
  • 1
  • 18
  • 25
  • That get Mime mapping is nice, but isn't it a heave process to figure out what is the type of file in run time? – Mohammed Noureldin Dec 16 '17 at 02:01
  • @MohammedNoureldin it is not "figuring" it, there is a simple mapping table based on file extensions or something like that. The server does it for all static files, it is not slow. – Al Kepp Jul 26 '19 at 09:59
13

Phil Haack has a nice article where he created a Custom File Download Action Result class. You only need to specify the virtual path of the file and the name to be saved as.

I used it once and here's my code.

        [AcceptVerbs(HttpVerbs.Get)]
        public ActionResult Download(int fileID)
        {
            Data.LinqToSql.File file = _fileService.GetByID(fileID);

            return new DownloadResult { VirtualPath = GetVirtualPath(file.Path),
                                        FileDownloadName = file.Name };
        }

In my example i was storing the physical path of the files so i used this helper method -that i found somewhere i can't remember- to convert it to a virtual path

        private string GetVirtualPath(string physicalPath)
        {
            string rootpath = Server.MapPath("~/");

            physicalPath = physicalPath.Replace(rootpath, "");
            physicalPath = physicalPath.Replace("\\", "/");

            return "~/" + physicalPath;
        }

Here's the full class as taken from Phill Haack's article

public class DownloadResult : ActionResult {

    public DownloadResult() {}

    public DownloadResult(string virtualPath) {
        this.VirtualPath = virtualPath;
    }

    public string VirtualPath {
        get;
        set;
    }

    public string FileDownloadName {
        get;
        set;
    }

    public override void ExecuteResult(ControllerContext context) {
        if (!String.IsNullOrEmpty(FileDownloadName)) {
            context.HttpContext.Response.AddHeader("content-disposition", 
            "attachment; filename=" + this.FileDownloadName)
        }

        string filePath = context.HttpContext.Server.MapPath(this.VirtualPath);
        context.HttpContext.Response.TransmitFile(filePath);
    }
}
Bernard Vander Beken
  • 4,315
  • 5
  • 46
  • 70
Manaf Abu.Rous
  • 2,337
  • 19
  • 24
  • 1
    Right, yes, I saw that article too, but it seems to do sort of the same thing as the article I used (see the reference to my previous post), and he says himself at the top of the page that the workaround shouldn't be needed anymore because: "NEW UPDATE: There is no longer need for this custom ActionResult because ASP.NET MVC now includes one in the box." But unfortunately, he doesn't say anything else about how this is to be used. – Anders Aug 31 '10 at 15:39
  • @ManafAbuRous, if you read the code closely you will see it actually converts the virtual path to physical path (`Server.MapPath(this.VirtualPath)`) so consuming this directly without change is a tad naive. You should produce an alternate that accepts `PhysicalPath` given that's what is eventually required and is what you are storing. This would be much safer as you have made an assumption that the physical path and relative path would be the same (excluding the root). Data files are often stored is App_Data. This is not accessible as a relative path. – Paul Fleming Sep 01 '13 at 11:13
  • GetVirtualPath is great.... very useful. thank you! – Zvi Redler Jun 15 '20 at 09:03
6

Thanks to Ian Henry!

In case if you need to get file from MS SQL Server here is the solution.

public FileResult DownloadDocument(string id)
        {
            if (!string.IsNullOrEmpty(id))
            {
                try
                {
                    var fileId = Guid.Parse(id);

                    var myFile = AppModel.MyFiles.SingleOrDefault(x => x.Id == fileId);

                    if (myFile != null)
                    {
                        byte[] fileBytes = myFile.FileData;
                        return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, myFile.FileName);
                    }
                }
                catch
                {
                }
            }

            return null;
        }

Where AppModel is EntityFramework model and MyFiles presents table in your database. FileData is varbinary(MAX) in MyFiles table.

DmitryBoyko
  • 32,983
  • 69
  • 281
  • 458
2

its simple just give your physical path in directoryPath with file name

public FilePathResult GetFileFromDisk(string fileName)
{
    return File(directoryPath, "multipart/form-data", fileName);
}
benka
  • 4,662
  • 35
  • 44
  • 58
0
   public ActionResult Download()
        {
            var document = //Obtain document from database context
    var cd = new System.Net.Mime.ContentDisposition
    {
        FileName = document.FileName,
        Inline = false,
    };
            Response.AppendHeader("Content-Disposition", cd.ToString());
            return File(document.Data, document.ContentType);
        }
-1

if (string.IsNullOrWhiteSpace(fileName)) return Content("filename not present");

        var path = Path.Combine(your path, your filename);

        var stream = new FileStream(path, FileMode.Open);

        return File(stream, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
-4

GetFile should be closing the file (or opening it within a using). Then you can delete the file after conversion to bytes-- the download will be done on that byte buffer.

    byte[] GetFile(string s)
    {
        byte[] data;
        using (System.IO.FileStream fs = System.IO.File.OpenRead(s))
        {
            data = new byte[fs.Length];
            int br = fs.Read(data, 0, data.Length);
            if (br != fs.Length)
                throw new System.IO.IOException(s);
        }
        return data;
    }

So in your download method...

        byte[] fileBytes = GetFile(file);
        // delete the file after conversion to bytes
        System.IO.File.Delete(file);
        // have the file download dialog only display the base name of the file            return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, Path.GetFileName(file));
CDichter
  • 141
  • 2