FileResult是一個基於文件的ActionResult,利用FileResult我們可以很容易地將從某個物理文件的內容響應給客戶端。ASP.NET MVC定義了三個具體的FileResult,分別是FileContentResult、FilePathResult和FileStreamResul ...
FileResult是一個基於文件的ActionResult,利用FileResult我們可以很容易地將從某個物理文件的內容響應給客戶端。ASP.NET MVC定義了三個具體的FileResult,分別是FileContentResult、FilePathResult和FileStreamResult。在這篇文章中我們將探討三種具體的FileResult是如何將文件內容對請求進行響應的。
如下麵的代碼片斷所示,FileResult具有一個表示媒體類型的只讀屬性ContentType,該屬性在構造函數中被初始化。當我們基於某個物理文件創建相應的FileResult對象的時候應該根據文件的類型指定媒體類型,比如說目標文件是一個.jpg圖片,那麼對應的媒體類型為“image/jpeg”,對於一個.pdf文件,則採用“application/pdf”。
public abstract class FileResult : ActionResult { private string _fileDownloadName; protected FileResult(string contentType) { if (string.IsNullOrEmpty(contentType)) { throw new ArgumentException(MvcResources.Common_NullOrEmpty, "contentType"); } this.ContentType = contentType; } public override void ExecuteResult(ControllerContext context) { if (context == null) { throw new ArgumentNullException("context"); } HttpResponseBase response = context.HttpContext.Response; response.ContentType = this.ContentType; if (!string.IsNullOrEmpty(this.FileDownloadName)) { string headerValue = ContentDispositionUtil.GetHeaderValue(this.FileDownloadName); context.HttpContext.Response.AddHeader("Content-Disposition", headerValue); } this.WriteFile(response); } protected abstract void WriteFile(HttpResponseBase response); public string ContentType { get; private set; } public string FileDownloadName { get { return (this._fileDownloadName ?? string.Empty); } set { this._fileDownloadName = value; } } internal static class ContentDispositionUtil { private const string HexDigits = "0123456789ABCDEF"; private static void AddByteToStringBuilder(byte b, StringBuilder builder) { builder.Append('%'); int num = b; AddHexDigitToStringBuilder(num >> 4, builder); AddHexDigitToStringBuilder(num % 0x10, builder); } private static void AddHexDigitToStringBuilder(int digit, StringBuilder builder) { builder.Append("0123456789ABCDEF"[digit]); } private static string CreateRfc2231HeaderValue(string filename) { StringBuilder builder = new StringBuilder("attachment; filename*=UTF-8''"); foreach (byte num in Encoding.UTF8.GetBytes(filename)) { if (IsByteValidHeaderValueCharacter(num)) { builder.Append((char) num); } else { AddByteToStringBuilder(num, builder); } } return builder.ToString(); } public static string GetHeaderValue(string fileName) { foreach (char ch in fileName) { if (ch > '\x007f') { return CreateRfc2231HeaderValue(fileName); } } ContentDisposition disposition = new ContentDisposition { FileName = fileName }; return disposition.ToString(); } private static bool IsByteValidHeaderValueCharacter(byte b) { if ((0x30 <= b) && (b <= 0x39)) { return true; } if ((0x61 <= b) && (b <= 0x7a)) { return true; } if ((0x41 <= b) && (b <= 90)) { return true; } switch (b) { case 0x3a: case 0x5f: case 0x7e: case 0x24: case 0x26: case 0x21: case 0x2b: case 0x2d: case 0x2e: return true; } return false; } } }View Code
針對文件的響應具有兩種形式,即內聯(Inline)和附件(Attachment)。一般來說,前者會利用瀏覽器直接打開響應的文件,而後者會以獨立的文件下載到客戶端。對於後者,我們一般會為下載的文件指定一個文件名,這個文件名可以通過FileResult的FileDownloadName屬性來指定。文件響應在預設情況下採用內聯的方式,如果需要採用附件的形式,需要為響應創建一個名稱為Content-Disposition的報頭,該報頭值的格式為“attachment; filename={ FileDownloadName }”。
FileResult僅僅是一個抽象類,文件內容的輸出實現在抽象方法WriteFile中,該方法會在重寫的ExecuteResult方法中調用。如果FileDownloadName屬性不為空,意味著會採用附件的形式進行文件響應,FileResult會在重寫的ExecuteResult方法中進行Content-Disposition響應報頭的設置。如下麵的代碼片斷基本上體現了ExecuteResult方法在FileResult中的實現。
public override void ExecuteResult(ControllerContext context) { if (context == null) { throw new ArgumentNullException("context"); } HttpResponseBase response = context.HttpContext.Response; response.ContentType = this.ContentType; if (!string.IsNullOrEmpty(this.FileDownloadName)) { string headerValue = ContentDispositionUtil.GetHeaderValue(this.FileDownloadName); context.HttpContext.Response.AddHeader("Content-Disposition", headerValue); } this.WriteFile(response); }
ASP.NET MVC定義了三個具體的FileResult,分別是FileContentResult、FilePathResult和FileStreamResult,接下來我們對它們進行單獨介紹。
FileContentResult是針對文件內容創建的FileResult。如下麵的代碼片斷所示,FileContentResult具有一個位元組數組類型的只讀屬性FileContents表示響應文件的內容,該屬性在構造函數中指定。FileContentResult針對文件內容的響應實現也很簡單,從如下所示的WriteFile方法定義可以看出,它只是調用當前HttpResponse的OutputStream屬性的Write方法直接將表示文件內容的位元組數組寫入響應輸出流。
public class FileContentResult : FileResult { public FileContentResult(byte[] fileContents, string contentType) : base(contentType) { if (fileContents == null) { throw new ArgumentNullException("fileContents"); } this.FileContents = fileContents; } protected override void WriteFile(HttpResponseBase response) { response.OutputStream.Write(this.FileContents, 0, this.FileContents.Length); } public byte[] FileContents { get; private set; } }
public abstract class Controller : ControllerBase, IActionFilter, IAuthorizationFilter, IDisposable, IExceptionFilter, IResultFilter, IAsyncController, IController, IAsyncManagerContainer { protected internal FileContentResult File(byte[] fileContents, string contentType) { return this.File(fileContents, contentType, null); } protected internal virtual FileContentResult File(byte[] fileContents, string contentType, string fileDownloadName) { return new FileContentResult(fileContents, contentType) { FileDownloadName = fileDownloadName }; } }
抽象類Controller中定義瞭如上兩個File重載根據指定的位元組數組、媒體類型和下載文件名(可選)生成相應的FileContentResult。由於FileContentResult是根據位元組數組創建的,當我們需要動態生成響應文件內容(而不是從物理文件中讀取)時,FileContentResult是一個不錯的選擇。
從名稱可以看出,FilePathResult是一個根據物理文件路徑創建FileResult。如下麵的代碼片斷所示,表示響應文件的路徑通過只讀屬性FileName表示,該屬性在構造函數中被初始化。在實現的WriteFile方法中,FilePathResult直接將文件路徑作為參數調用當前HttpResponse的TransmitFile實現了針對文件內容的響應。抽象類Controller同樣定義了兩個File方法重載來根據文件路徑創建相應的FilePathResult。
public class FilePathResult : FileResult { public FilePathResult(string fileName, string contentType) : base(contentType) { if (string.IsNullOrEmpty(fileName)) { throw new ArgumentException(MvcResources.Common_NullOrEmpty, "fileName"); } this.FileName = fileName; } protected override void WriteFile(HttpResponseBase response) { response.TransmitFile(this.FileName); } public string FileName { get; private set; } }
public abstract class Controller : ControllerBase,... { protected internal FilePathResult File(string fileName, string contentType) { return this.File(fileName, contentType, null); } protected internal virtual FilePathResult File(string fileName, string contentType, string fileDownloadName) { return new FilePathResult(fileName, contentType) { FileDownloadName = fileDownloadName }; } ..... }
FileStreamResult允許我們通過一個用於讀取文件內容的流來創建FileResult。如下麵的代碼片斷所示,讀取文件流通過只讀屬性FileStream表示,該屬性在構造函數中被初始化。在實現的WriteFile方法中,FileStreamResult通過指定的文件流讀取文件內容,並最終調用當前HttpResponse的OutputStream屬性的Write方法將讀取的內容寫入當前HTTP響應的輸出流中。抽象類Controller中同樣定義了兩個File方法重載根據文件讀取流創建相應的FileStreamResult。
public class FileStreamResult : FileResult { private const int BufferSize = 0x1000; public FileStreamResult(Stream fileStream, string contentType) : base(contentType) { if (fileStream == null) { throw new ArgumentNullException("fileStream"); } this.FileStream = fileStream; } protected override void WriteFile(HttpResponseBase response) { Stream outputStream = response.OutputStream; using (this.FileStream) { byte[] buffer = new byte[0x1000]; while (true) { int count = this.FileStream.Read(buffer, 0, 0x1000); if (count == 0) { return; } outputStream.Write(buffer, 0, count); } } } public Stream FileStream { get; private set; } }
public abstract class Controller : ControllerBase, ... { protected internal FileStreamResult File(Stream fileStream, string contentType) { return this.File(fileStream, contentType, null); } protected internal virtual FileStreamResult File(Stream fileStream, string contentType, string fileDownloadName) { return new FileStreamResult(fileStream, contentType) { FileDownloadName = fileDownloadName }; } ... }
本文轉載自蔣老師的瞭解ASP.NET MVC幾種ActionResult的本質:FileResult