views:

56

answers:

2

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;
    }
+1  A: 

Phil Haack has a nice article where he created a Custome 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);
    }
}
Manaf Abu.Rous
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 Svensson
+1  A: 

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

public FileResult Download()
{
    byte[] fileBytes = ...;
    string fileName = "example";
    return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
}
Ian Henry
Ok, I could try that, but what goes into the byte[] array?
Anders Svensson
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 Svensson