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 }
针对文件的响应具有两种形式,内联(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 }
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 }
抽象类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 }
四、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的接受就到这里。