MVC&WebForm对照学习:文件下载

时间:2023-11-23 19:28:20

说完了WebForm和MVC中的文件上传,就不得不说用户从服务器端下载资源了。那么今天就扯扯在WebForm和MVC中是如何实现文件下载的。说起WebForm中的文件上传,codeshark在他的博文ASP.NET实现文件下载中讲到了ASP.NET中文件下载的4种方式。当然文章主要指的是在WebForm中的实现方式。总结得相当到位,那么这篇,就先来看看MVC中文件下载的方式。然后再回过头来看看它们实现方式的关联。

Part 1 MVC中的文件下载

在mvc中微软封装好了ActionResult的诸多的实现,这使我们根据需要灵活地选择操作结果来响应用户的操作。这其中一个很重要的实现就是.NET Framework 4.0中的FileResult。它表示一个用于将二进制文件内容发送到响应的基类。FileResult类有三个实现:FileContentResult、FileStreamResult、FilePathResult。mvc中的文件下载就是依赖这三个子类。不扯闲,先来看具体实现。

MVC&WebForm对照学习:文件下载 FileContentResult

public ActionResult FileDownLoad()
{
FileStream fs = new FileStream(Server.MapPath("~/uploads/Desert.jpg"), FileMode.Open, FileAccess.Read);
byte[] bytes = new byte[fs.Length];
fs.Read(bytes, 0, Convert.ToInt32(bytes.Length));
return File(bytes, "image/jpeg", "Desert.jpg");
}

MVC&WebForm对照学习:文件下载 FileStreamResult

public ActionResult FileDownLoad()
{
return File(new FileStream(Server.MapPath("~/uploads/Desert.jpg"), FileMode.Open, FileAccess.Read), "image/jpeg", "Desert.jpg");
}

MVC&WebForm对照学习:文件下载 FilePathResult

public ActionResult FileDownLoad()
{
return File(Server.MapPath("~/uploads/Desert.jpg"), "image/jpeg", "Desert.jpg");

看了上面的代码是不是感觉这似乎比codeshark文章中讲到的WebForm中的文件下载代码更简洁了?确实!那么我们同时也不禁要问:不是说mvc中的文件下载依赖于FileContentResult、FilePathResult、FileStreamResult吗,为什么这里边无一例外都是return File(......)呢?这个对于接触过mvc的你来说,相信难不倒你,F12一下就全明白了:

MVC&WebForm对照学习:文件下载(图1-1)

原来File(......)方法的背后返回的是FileContentResult、FileStreamResult、FilePathResult的实例。这就不足为怪了。那么也就是说其实上面的3钟实现方式你完全可以改成如下形式:

//FilePathResult
return new FilePathResult(Server.MapPath("~/uploads/Desert.jpg"), "image/jpeg") { FileDownloadName = "Desert.jpg" };
//FileStreamResult
return new FileStreamResult(new FileStream(Server.MapPath("~/uploads/Desert.jpg"), FileMode.Open, FileAccess.Read), "image/jpeg") { FileDownloadName = "Desert.jpg" };
//FileContentResult
return new FileContentResult(System.IO.File.ReadAllBytes(Server.MapPath("~/uploads/Desert.jpg")), "image/jpeg") { FileDownloadName = "Desert.jpg" };

如果你通过ILSpay查看源码的话,你会发现其实对应的return File(......)的内部实现就是如此。

MVC&WebForm对照学习:文件下载问题1:不给fileDownloadNane赋值效果会如何

回过头来再看下图1-1,我们还会发现FileContentResult、FileStreamResult、FilePathResult的下载方法,还各自对应的存在一个没有第三个参数fileDownloadNane的方法重载。那么这个方法又是用来干嘛呢?小段代码看下便知:

public ActionResult FileDownLoad()
{
return File(Server.MapPath("~/uploads/Desert.jpg")
}

--------------------------------------------------------------------------------运行结果-------------------------------------------------------------------------------------------------------------------

MVC&WebForm对照学习:文件下载

通过运行结果截图一眼就能看出,此时将图片直接输出在页面上,这实现图片的显示功能。那么同时我们也能够知道,同样的这样也是可以的:

return new FilePathResult(Server.MapPath("~/uploads/Desert.jpg"), "image/jpeg") ;

MVC&WebForm对照学习:文件下载问题2:加不加fileDownloadNane背后到底做了什么

当然其它两种方式同样如此。那么我们不禁要问:为什么给不给fileDownloadName赋值,实现的效果完全不一样呢?那么你肯定想到了在内部肯定做了什么来加以区分。那么就以FilePathResult为例,用ILSpy来看看究竟:

MVC&WebForm对照学习:文件下载(图1-2)

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

MVC&WebForm对照学习:文件下载(图1-3)

先看图1-2在FileResult类中有一个FileDownloadName公开属性,我们知道ActionResult类最终要执行子类的ExecuteResult方法,而在FileResultExecuteResult方法中,对FileDownloadName进行了非空判断。如果不为空则通过Response.AddHeader()方法向客户端浏览器发送文件。如果为空则调用子类FilePathResult的重写方法WriteFile() (图1-3所示)直接向页面输出响应流。当然FileContentResult、FileStreamResult道理相同。

MVC&WebForm对照学习:文件下载问题3:FileContentResult、FileStreamResult、FilePathResult文件下载的方式到底有什么不同

嗯,要回答这个问题,不用说,还是得看看它的内部实现。那么我们就一个个地来看看究竟。

图1-2和图1-3种已经看得很清楚,最终文件下载时指定了FileResultExecuteResult方法,而在ExecuteResult方法中调用了FileResult的子类的WriteFile方法。那么我只需要查看每个子类的WriteFile方法便知道了。

FileContentResult

MVC&WebForm对照学习:文件下载

FileStreamResult

MVC&WebForm对照学习:文件下载

FilePathResult

MVC&WebForm对照学习:文件下载

再去看看codeshark的文章ASP.NET实现文件下载,在这篇文章中他总结了文件下载的4种方式:

方式一:TransmitFile实现下载。将指定的文件直接写入 HTTP 响应输出流,而不在内存中缓冲该文件。

方式二:WriteFile实现下载,将指定的文件直接写入 HTTP 响应输出流。注意:对大型文件使用此方法时,调用此方法可能导致异常。可以使用此方法的文件大小取决于 Web 服务器的硬件配置。

方式三:WriteFile分块下载

方式四:Response.BinaryWrite()流方式下载

那么与mvc中文件下载对照一下,不难发现,FilePathResult实际上是采用的Response.TransmitFile的方式实现下载的;而FileStreamResult是采用的WriteFile分块下载;FileContentResult在文章中没有直接的对照方式,而它采用的是Response.OutputStream.Write的方式。另外Response.BinaryWrite()流的方式在mvc中没有实现。当然在msdn上给出了实现我直接贴出实现代码:

实现代码:

public class BinaryContentResult : ActionResult
{
public BinaryContentResult()
{
} // Properties for encapsulating http headers.
public string ContentType { get; set; }
public string FileName { get; set; }
public byte[] Content { get; set; } // The code sets the http headers and outputs the file content.
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.ClearContent();
context.HttpContext.Response.ContentType = ContentType; context.HttpContext.Response.AddHeader("content-disposition",
"attachment; filename=" + FileName); context.HttpContext.Response.BinaryWrite(Content);
context.HttpContext.Response.End();
}
}

调用代码:

public ActionResult Download(string fn)
{
// Check whether the requested file is valid.
string pfn = Server.MapPath("~/App_Data/download/" + fn);
if (!System.IO.File.Exists(pfn))
{
throw new ArgumentException("Invalid file name or file not exists!");
} // Use BinaryContentResult to encapsulate the file content and return it.
return new BinaryContentResult()
{
FileName = fn,
ContentType = "application/octet-stream",
Content = System.IO.File.ReadAllBytes(pfn)
};
}

MVC&WebForm对照学习:文件下载问题4:那么FileContentResult的Response.OutputStream.Write()和Response.WriteFile()又有什么区别昵

关于这个问题,我查了很久,没有得到比较满翼的答案,在这里希望大神指点一二!

Part 2 WebForm中的文件下载

看看这篇文章ASP.NET实现文件下载一目了然。四种方式不多说了。到这里,不得不说MVC中的文件下载is so easy!这还得归功于微软的封装。那么问题来了,在WebForm中我们是不是因该也封装一个这样的实现,这样在以后使用的时候不用写(当然一般是copy)重复写这么写这些容易忘记的代码了昵? Of course, just do it! 网上我没有找到一个针对这个实现的封装(有大神代码可以贡献一下)。我索性自己写一个,好不好,就不说了。

1.定义抽象类

先定义一个抽象列FileDownloader,定义公共的下载行为WriteFile方法和执执行下载的Execute方法以及文件下载名_fileDownloadName。代码如下:

public abstract class FileDownloader
{
private string _fileDownloadName;
public FileDownloader(string contentType)
{
if (string.IsNullOrEmpty(contentType))
{
throw new ArgumentException("contentType");
}
this.ContentType = contentType;
}
public void Execute(HttpContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
HttpResponse response = context.Response;
response.ClearContent();
response.ContentType = this.ContentType;
if (!string.IsNullOrEmpty(this.FileDownloadName))
{
context.Response.AddHeader("content-disposition",
"attachment; filename=" + this.FileDownloadName);
}
this.WriteFile(context.Response);
response.Flush();
response.End();
}
protected abstract void WriteFile(HttpResponse response);
public string ContentType { get; private set; }
public string FileDownloadName
{
get
{
return (this._fileDownloadName ?? string.Empty);
}
set
{
this._fileDownloadName = value;
}
}
}

2.创建实现类

此处模拟MVC中的实现方式创建对应的WebForm中的实现方式,为了易于区分说明,我采取和MVC中同样的类名。

MVC中FileContentResult的下载方式(区别于FileStreamResult分区下载),这里实现一下:

public class FileContentResult : FileDownloader
{
public FileContentResult(byte[] fileContents, string contentType)
: base(contentType)
{
if (fileContents == null)
{
throw new ArgumentNullException("fileContents");
}
this.FileContents = fileContents;
}
public byte[] FileContents { get; private set; }
protected override void WriteFile(HttpResponse response)
{
response.OutputStream.Write(this.FileContents, 0, this.FileContents.Length);
}
}

FileStreamResult(分区下载):

public class FileStreamResult : FileDownloader
{
public FileStreamResult(Stream fileStream, string contentType)
: base(contentType)
{
if (fileStream == null)
{
throw new ArgumentNullException("fileStream");
}
this.FileStream = fileStream;
}
public Stream FileStream { get; private set; }
protected override void WriteFile(HttpResponse response)
{
Stream outputStream = response.OutputStream;
using (this.FileStream)
{
byte[] buffer = new byte[4096];
while (true)
{
int num = this.FileStream.Read(buffer, 0, 4096);
if (num == 0)
{
break;
}
outputStream.Write(buffer, 0, num);
}
}
}
}

FIlePathResult:

public class FIlePathResult : FileDownloader
{
public FIlePathResult(string fileName, string contentType)
: base(contentType)
{
if (string.IsNullOrEmpty(fileName))
{
throw new ArgumentNullException("fileName");
}
this.FileName = fileName;
}
public string FileName { get; private set; } protected override void WriteFile(HttpResponse response)
{
response.TransmitFile(this.FileName);
}
}

另外上面我们也采用了微软上BinaryContentResult的实现,这里同样也实现一下BinaryContentResult:

public class BinaryContentResult : FileDownloader
{
public BinaryContentResult(byte[] fileContents, string contentType)
: base(contentType)
{
if (fileContents == null)
{
throw new ArgumentNullException("fileContents");
}
this.FileContents = fileContents;
} protected override void WriteFile(HttpResponse response)
{
response.BinaryWrite(FileContents);
} public byte[] FileContents { get; private set; }
}

另外还有HttpResponse.WriteFile(net 2.0中的提出的,对大型文件使用此方法时,调用此方法可能会引发异常这里姑且称之为旧的实现方式:

public class FilePathOldResult : FileDownloader
{
public FilePathOldResult(string fileName, string contentType)
: base(contentType)
{
if (string.IsNullOrEmpty(fileName))
{
throw new ArgumentNullException("fileName");
}
this.FileName = fileName;
}
public string FileName { get; private set; }
protected override void WriteFile(HttpResponse response)
{
response.WriteFile(this.FileName);
}

Part 3 问题开发

在文件下载中,我们可能需要自动获取文件的MIME类型,这在指定文件的下载类型时。那么如何获取文件的MIME类型呢?在Mitchell Chu的博客.NET获取文件的MIME类型(Content Type)中给出了比较好的答案。这里就不做赘述。

Part 4 The end

回过头来看还是那句话,因为MVC和WebForm都是基于ASP.NET框架,因此文件下载功能的背后还是采用了相同的组件实现。

注:由于个人技术有限,对某些概念的理解可能会存在偏差,如果你发现本文存在什么bug,请指正。谢谢!

完。