FileResult是一個基於文件的ActionResult,利用FileResult,咱們能夠很容易的將某個物理文件的內容響應給客戶端,ASP.NET MVC定義了三個具體的FileResult,分別是 FileContentResult、FilePathResult、FileStreamResult。在這篇文章中,咱們來探討一下三種具體的FileResult是如何將文件內容對請求進行響應的。git
1、FileResult數組
以下面的代碼片斷所示,FileResult具備一個表示媒體類型的只讀屬性ContentType,該屬性在構造函數中被初始化。當咱們基於某個物理文件建立響應的FileResult對象的時候,應該根據文件的類型指定媒體類型,好比說,目標文件是一個.JPG圖片,那麼對應的媒體類型就應該是「image/jpeg」,對於一個.pdf文件,則採用「application/pdf」。瀏覽器
1 public abstract class FileResult : ActionResult 2 { 3 private string _fileDownloadName; 4
5 protected FileResult(string contentType) 6 { 7 if (string.IsNullOrEmpty(contentType)) 8 { 9 throw new ArgumentException(MvcResources.Common_NullOrEmpty, "contentType"); 10 } 11 this.ContentType = contentType; 12 } 13
14 public override void ExecuteResult(ControllerContext context) 15 { 16 if (context == null) 17 { 18 throw new ArgumentNullException("context"); 19 } 20 HttpResponseBase response = context.HttpContext.Response; 21 response.ContentType = this.ContentType; 22 if (!string.IsNullOrEmpty(this.FileDownloadName)) 23 { 24 string headerValue = ContentDispositionUtil.GetHeaderValue(this.FileDownloadName); 25 context.HttpContext.Response.AddHeader("Content-Disposition", headerValue); 26 } 27 this.WriteFile(response); 28 } 29
30 protected abstract void WriteFile(HttpResponseBase response); 31
32 public string ContentType { get; private set; } 33
34 public string FileDownloadName 35 { 36 get
37 { 38 return (this._fileDownloadName ?? string.Empty); 39 } 40 set
41 { 42 this._fileDownloadName = value; 43 } 44 } 45
46 internal static class ContentDispositionUtil 47 { 48 private const string HexDigits = "0123456789ABCDEF"; 49
50 private static void AddByteToStringBuilder(byte b, StringBuilder builder) 51 { 52 builder.Append('%'); 53 int num = b; 54 AddHexDigitToStringBuilder(num >> 4, builder); 55 AddHexDigitToStringBuilder(num % 0x10, builder); 56 } 57
58 private static void AddHexDigitToStringBuilder(int digit, StringBuilder builder) 59 { 60 builder.Append("0123456789ABCDEF"[digit]); 61 } 62
63 private static string CreateRfc2231HeaderValue(string filename) 64 { 65 StringBuilder builder = new StringBuilder("attachment; filename*=UTF-8''"); 66 foreach (byte num in Encoding.UTF8.GetBytes(filename)) 67 { 68 if (IsByteValidHeaderValueCharacter(num)) 69 { 70 builder.Append((char) num); 71 } 72 else
73 { 74 AddByteToStringBuilder(num, builder); 75 } 76 } 77 return builder.ToString(); 78 } 79
80 public static string GetHeaderValue(string fileName) 81 { 82 foreach (char ch in fileName) 83 { 84 if (ch > '\x007f') 85 { 86 return CreateRfc2231HeaderValue(fileName); 87 } 88 } 89 ContentDisposition disposition = new ContentDisposition { 90 FileName = fileName 91 }; 92 return disposition.ToString(); 93 } 94
95 private static bool IsByteValidHeaderValueCharacter(byte b) 96 { 97 if ((0x30 <= b) && (b <= 0x39)) 98 { 99 return true; 100 } 101 if ((0x61 <= b) && (b <= 0x7a)) 102 { 103 return true; 104 } 105 if ((0x41 <= b) && (b <= 90)) 106 { 107 return true; 108 } 109 switch (b) 110 { 111 case 0x3a: 112 case 0x5f: 113 case 0x7e: 114 case 0x24: 115 case 0x26: 116 case 0x21: 117 case 0x2b: 118 case 0x2d: 119 case 0x2e: 120 return true; 121 } 122 return false; 123 } 124 } 125 }
針對文件的響應具備兩種形式,內聯(Inline)和附件(Attachment)。通常來講,前者會利用瀏覽器直接打開響應文件,然後者則會以獨立的文件下載到客戶端。對於後者,咱們通常會爲下載的文件指定一個文件名,這個文件名能夠經過FileResult的FileDownloadName屬性來指定。文件響應在默認狀況下采用內聯方式,若是須要採用附件的形式,須要爲響應建立一個名爲Content-Disposition的報頭,該報頭值的格式爲「attachment;filename={FileDownloadName}」。app
FileResult僅僅是一個抽象類,文件內容的輸出實如今抽象方法WriteFile中,該方法會在重寫的ExecuteResult方法中調用。若是FileDownloadName屬性不爲空,意味着會採用附件的形式進行文件響應,FileResult會在重寫的ExecuteResult方法中進行Content-Disposition響應報頭的設置。以下面的代碼片斷,基本上體現了ExecuteResult方法在FileResult中的體現。ide
1 public override void ExecuteResult(ControllerContext context) 2 { 3 if (context == null) 4 { 5 throw new ArgumentNullException("context"); 6 } 7 HttpResponseBase response = context.HttpContext.Response; 8 response.ContentType = this.ContentType; 9 if (!string.IsNullOrEmpty(this.FileDownloadName)) 10 { 11 string headerValue = ContentDispositionUtil.GetHeaderValue(this.FileDownloadName); 12 context.HttpContext.Response.AddHeader("Content-Disposition", headerValue); 13 } 14 this.WriteFile(response); 15 }
ASP.NET MVC定義了三個具體的FileResult,分別是FileContentResult、FilePathResult、FileStreamResult。接下來咱們對他們進行單獨介紹。函數
2、FileContentResultui
FileContentResult是針對文件內容建立的FileResult。以下面的代碼片斷所示,FileContentResult具備一個字節數組類型的只讀屬性FileContents表示響應文件的內容,該屬性在構造函數中指定。FileContentResult針對文件內容的響應實現也很簡單,從以下示的WriteFile方法定義能夠看出,它只是調用當前HttpResponse的OutputStream屬性的Write方法直接將表示文件內容的字節數組寫入響應輸出流。this
1 public class FileContentResult : FileResult 2 { 3 public FileContentResult(byte[] fileContents, string contentType) : base(contentType) 4 { 5 if (fileContents == null) 6 { 7 throw new ArgumentNullException("fileContents"); 8 } 9 this.FileContents = fileContents; 10 } 11
12 protected override void WriteFile(HttpResponseBase response) 13 { 14 response.OutputStream.Write(this.FileContents, 0, this.FileContents.Length); 15 } 16
17 public byte[] FileContents { get; private set; } 18 } 19 public abstract class Controller : ControllerBase, IActionFilter, IAuthorizationFilter, IDisposable, IExceptionFilter, IResultFilter, IAsyncController, IController, IAsyncManagerContainer 20 { 21 protected internal FileContentResult File(byte[] fileContents, string contentType) 22 { 23 return this.File(fileContents, contentType, null); 24 } 25 protected internal virtual FileContentResult File(byte[] fileContents, string contentType, string fileDownloadName) 26 { 27 return new FileContentResult(fileContents, contentType) { FileDownloadName = fileDownloadName }; 28 } 29 }
抽象類Controller中定義瞭如上兩個File重載根據指定的字節數組、媒體類型和下載文件名(可選)生成相應的FileContentResult。因爲FileContentResult是根據字節數組建立的,當咱們須要動態生成響應文件內容(而不是從物理文件中讀取)時,FileContentResult是一個不錯的選擇。spa
3、FilePathResult3d
從名稱能夠看出,FilePathResult是一個根據物理文件路徑建立FileResult。以下面的代碼片斷所示,表示響應文件的路徑經過只讀屬性FileName表示,該屬性在構造函數中被初始化。在實現的WriteFile方法中,FilePathResult直接將文件路徑做爲參數調用當前HttpResponse的TransmiteFile實現了針對文件內容的響應。抽象類Controller一樣定義了兩個File方法重載來根據文件路徑建立相應的FilePathResult。
1 public class FilePathResult : FileResult 2 { 3 public FilePathResult(string fileName, string contentType) : base(contentType) 4 { 5 if (string.IsNullOrEmpty(fileName)) 6 { 7 throw new ArgumentException(MvcResources.Common_NullOrEmpty, "fileName"); 8 } 9 this.FileName = fileName; 10 } 11
12 protected override void WriteFile(HttpResponseBase response) 13 { 14 response.TransmitFile(this.FileName); 15 } 16
17 public string FileName { get; private set; } 18 } 19 public abstract class Controller : ControllerBase,... 20 { 21 protected internal FilePathResult File(string fileName, string contentType) 22 { 23 return this.File(fileName, contentType, null); 24 } 25 protected internal virtual FilePathResult File(string fileName, string contentType, string fileDownloadName) 26 { 27 return new FilePathResult(fileName, contentType) { FileDownloadName = fileDownloadName }; 28 } 29 ..... 30 }
4、FileStreamResult
FileStreamResult容許咱們經過一個用於讀取文件內容的流來建立FileResult。以下面的代碼片斷所示,讀取文件流經過只讀屬性FileStream表示,該屬性在構造函數中被初始化。在實現的WriteFile方法中,FileStreamResult經過指定的文件流讀取文件內容,並最終調用當前HttpResponse的OutputStream屬性和Write方法將讀取的內容寫入當前Http相應的輸出流中。抽象類Controller中一樣定義了兩個File方法重載根據文件杜宇流建立相應的FileStreamResult。
1 public class FileStreamResult : FileResult 2 { 3 private const int BufferSize = 0x1000; 4
5 public FileStreamResult(Stream fileStream, string contentType) : base(contentType) 6 { 7 if (fileStream == null) 8 { 9 throw new ArgumentNullException("fileStream"); 10 } 11 this.FileStream = fileStream; 12 } 13
14 protected override void WriteFile(HttpResponseBase response) 15 { 16 Stream outputStream = response.OutputStream; 17 using (this.FileStream) 18 { 19 byte[] buffer = new byte[0x1000]; 20 while (true) 21 { 22 int count = this.FileStream.Read(buffer, 0, 0x1000); 23 if (count == 0) 24 { 25 return; 26 } 27 outputStream.Write(buffer, 0, count); 28 } 29 } 30 } 31
32 public Stream FileStream { get; private set; } 33 } 34 public abstract class Controller : ControllerBase, ... 35 { 36 protected internal FileStreamResult File(Stream fileStream, string contentType) 37 { 38 return this.File(fileStream, contentType, null); 39 } 40 protected internal virtual FileStreamResult File(Stream fileStream, string contentType, string fileDownloadName) 41 { 42 return new FileStreamResult(fileStream, contentType) { FileDownloadName = fileDownloadName }; 43 } 44 ... 45 }
以上即是FileResult的三個子類。好了,關於FileResult的接受就到這裏。