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;
}
Response.ContentType = MimeMapping.GetMimeMapping(filePath);
, from stackoverflow.com/a/22231074/4573839
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);
}
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));
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);
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);
}
}
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.
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.
its simple just give your physical path in directoryPath with file name
public FilePathResult GetFileFromDisk(string fileName)
{
return File(directoryPath, "multipart/form-data", fileName);
}
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);
}
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);
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));
Success story sharing
ReadAllBytes
was added years later in an edit. Why is this my second most upvoted answer? Oh well.non-invocable member "File" cannot be used like a method.