• 浅谈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的接受就到这里。

  • 相关阅读:
    mybatis框架demo first
    pro02总结:spring mvc + jdbc
    java开发常用jar包介绍(转载)
    proj01总结:spring jdbc操作
    mysql导入sql文件
    hibernate对象三种状态
    Hibernate 的saveOrUpdate方法(转)
    java web面试题,收集
    redis与spring整合·
    mybatis_2
  • 原文地址:https://www.cnblogs.com/xiaomowang/p/12326294.html
Copyright © 2020-2023  润新知