你的位置:首页 > ASP.net教程

[ASP.net教程]ASP.NET MVC 了解FileResult的本质


FileResult是一个基于文件的ActionResult,利用FileResult我们可以很容易地将从某个物理文件的内容响应给客户端。ASP.NET MVC定义了三个具体的FileResult,分别是FileContentResult、FilePathResult和FileStreamResult。在这篇文章中我们将探讨三种具体的FileResult是如何将文件内容对请求进行响应的。

一、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

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

从名称可以看出,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

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