說完了WebForm和MVC中的文件上傳,就不得不說用戶從服務器端下載資源了。那麼今天就扯扯在WebForm和MVC中是如何實現文件下載的。提及WebForm中的文件上傳,codeshark在他的博文ASP.NET實現文件下載中講到了ASP.NET中文件下載的4種方式。固然文章主要指的是在WebForm中的實現方式。總結得至關到位,那麼這篇,就先來看看MVC中文件下載的方式。而後再回過頭來看看它們實現方式的關聯。html
Part 1 MVC中的文件下載web
在mvc中微軟封裝好了ActionResult的諸多的實現,這使咱們根據須要靈活地選擇操做結果來響應用戶的操做。這其中一個很重要的實現就是.NET Framework 4.0中的FileResult。它 表示一個用於將二進制文件內容發送到響應的基類。FileResult類有三個實現:FileContentResult、FileStreamResult、FilePathResult。mvc中的文件下載就是依賴這三個子類。不扯閒,先來看具體實現。瀏覽器
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"); }
FileStreamResultmvc
public ActionResult FileDownLoad() { return File(new FileStream(Server.MapPath("~/uploads/Desert.jpg"), FileMode.Open, FileAccess.Read), "image/jpeg", "Desert.jpg"); }
FilePathResultapp
public ActionResult FileDownLoad() { return File(Server.MapPath("~/uploads/Desert.jpg"), "image/jpeg", "Desert.jpg");
看了上面的代碼是否是感受這彷佛比codeshark文章中講到的WebForm中的文件下載代碼更簡潔了?確實!那麼咱們同時也不由要問:不是說mvc中的文件下載依賴於FileContentResult、FilePathResult、FileStreamResult嗎,爲何這裏邊無一例外都是return File(......)呢?這個對於接觸過mvc的你來講,相信難不倒你,F12一下就全明白了:框架
(圖1-1)asp.net
原來File(......)方法的背後返回的是FileContentResult、FileStreamResult、FilePathResult的實例。這就不足爲怪了。那麼也就是說其實上面的3鍾實現方式你徹底能夠改爲以下形式:ide
//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(......)的內部實現就是如此。ui
問題1:不給fileDownloadNane賦值效果會如何
回過頭來再看下圖1-1,咱們還會發現FileContentResult、FileStreamResult、FilePathResult的下載方法,還各自對應的存在一個沒有第三個參數fileDownloadNane的方法重載。那麼這個方法又是用來幹嗎呢?小段代碼看下便知:
public ActionResult FileDownLoad() { return File(Server.MapPath("~/uploads/Desert.jpg") }
--------------------------------------------------------------------------------運行結果-------------------------------------------------------------------------------------------------------------------
經過運行結果截圖一眼就能看出,此時將圖片直接輸出在頁面上,這實現圖片的顯示功能。那麼同時咱們也可以知道,一樣的這樣也是能夠的:
return new FilePathResult(Server.MapPath("~/uploads/Desert.jpg"), "image/jpeg") ;
問題2:加不加fileDownloadNane背後到底作了什麼
固然其它兩種方式一樣如此。那麼咱們不由要問:爲何給不給fileDownloadName賦值,實現的效果徹底不同呢?那麼你確定想到了在內部確定作了什麼來加以區分。那麼就以FilePathResult爲例,用ILSpy來看看究竟:
(圖1-2)
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
(圖1-3)
先看圖1-2在FileResult類中有一個FileDownloadName公開屬性,咱們知道ActionResult類最終要執行子類的ExecuteResult方法,而在FileResult的ExecuteResult方法中,對FileDownloadName進行了非空判斷。若是不爲空則經過Response.AddHeader()方法向客戶端瀏覽器發送文件。若是爲空則調用子類FilePathResult的重寫方法WriteFile() (圖1-3所示)直接向頁面輸出響應流。固然FileContentResult、FileStreamResult道理相同。
問題3:FileContentResult、FileStreamResult、FilePathResult文件下載的方式到底有什麼不一樣
嗯,要回答這個問題,不用說,仍是得看看它的內部實現。那麼咱們就一個個地來看看究竟。
圖1-2和圖1-3種已經看得很清楚,最終文件下載時指定了FileResult的ExecuteResult方法,而在ExecuteResult方法中調用了FileResult的子類的WriteFile方法。那麼我只須要查看每一個子類的WriteFile方法便知道了。
FileContentResult
FileStreamResult
FilePathResult
再去看看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) }; }
問題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,請指正。謝謝!
完。