淺談ActionResult之FileResult


FileResult是一個基於文件的ActionResult,利用FileResult,我們可以很容易的將某個物理文件的內容響應給客戶端,ASP.NET MVC定義了三個具體的FileResult,分別是 FileContentResult、FilePathResult、FileStreamResult。在這篇文章中,我們來探討一下三種具體的FileResult是如何將文件內容對請求進行響應的。

一、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 }
View Code

 針對文件的響應具有兩種形式,內聯(Inline)和附件(Attachment)。一般來說,前者會利用瀏覽器直接打開響應文件,而后者則會以獨立的文件下載到客戶端。對於后者,我們一般會為下載的文件指定一個文件名,這個文件名可以通過FileResult的FileDownloadName屬性來指定。文件響應在默認情況下采用內聯方式,如果需要采用附件的形式,需要為響應創建一個名為Content-Disposition的報頭,該報頭值的格式為“attachment;filename={FileDownloadName}”。

FileResult僅僅是一個抽象類,文件內容的輸出實現在抽象方法WriteFile中,該方法會在重寫的ExecuteResult方法中調用。如果FileDownloadName屬性不為空,意味着會采用附件的形式進行文件響應,FileResult會在重寫的ExecuteResult方法中進行Content-Disposition響應報頭的設置。如下面的代碼片段,基本上體現了ExecuteResult方法在FileResult中的體現。

 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 }
View Code

ASP.NET MVC定義了三個具體的FileResult,分別是FileContentResult、FilePathResult、FileStreamResult。接下來我們對他們進行單獨介紹。

二、FileContentResult

FileContentResult是針對文件內容創建的FileResult。如下面的代碼片段所示,FileContentResult具有一個字節數組類型的只讀屬性FileContents表示響應文件的內容,該屬性在構造函數中指定。FileContentResult針對文件內容的響應實現也很簡單,從如下示的WriteFile方法定義可以看出,它只是調用當前HttpResponse的OutputStream屬性的Write方法直接將表示文件內容的字節數組寫入響應輸出流。

 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 }
View Code

抽象類Controller中定義了如上兩個File重載根據指定的字節數組、媒體類型和下載文件名(可選)生成相應的FileContentResult。由於FileContentResult是根據字節數組創建的,當我們需要動態生成響應文件內容(而不是從物理文件中讀取)時,FileContentResult是一個不錯的選擇。

三、FilePathResult

從名稱可以看出,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 } 
View Code

四、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 }
View Code

以上便是FileResult的三個子類。好了,關於FileResult的接受就到這里。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM