ChatGPT解决这个技术问题 Extra ChatGPT

Download file of any type in Asp.Net MVC using FileResult?

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;
}
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.
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.
Are there any tools that allow you to find potentially dangerous loopholes such as this one?
I find that it's convenient to set content-type as Response.ContentType = MimeMapping.GetMimeMapping(filePath);, from stackoverflow.com/a/22231074/4573839
What are you using on client side?

T
Tieson T.

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

Ok, I could try that, but what goes into the byte[] array?
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!
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.
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.
Getting this error: non-invocable member "File" cannot be used like a method.
J
Jonathan

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

S
Salman Hasrat Khan

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

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?
@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.
B
Bernard Vander Beken

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

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.
@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.
GetVirtualPath is great.... very useful. thank you!
N
NoWar

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.


b
benka

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

public FilePathResult GetFileFromDisk(string fileName)
{
    return File(directoryPath, "multipart/form-data", fileName);
}

What about client side, calling this method? Lets say if you want to show save as dialog?
h
hussein zakizadeh
   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);
        }

C
Caio Augusto

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

C
CDichter

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

Please don't ever, ever load entire files into memory into production like this

关注公众号,不定期副业成功案例分享
Follow WeChat

Success story sharing

Want to stay one step ahead of the latest teleworks?

Subscribe Now