zoukankan      html  css  js  c++  java
  • ASP.NET MVC 了解FileResult的本质

    FileResult是一个基于文件的ActionResult,利用FileResult我们可以很容易地将从某个物理文件的内容响应给客户端。ASP.NET MVC定义了三个具体的FileResult,分别是FileContentResultFilePathResultFileStreamResult。在这篇文章中我们将探讨三种具体的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

  • 相关阅读:
    模拟登陆江西理工大学教务系统
    python3爬虫 -----华东交大校园新闻爬取与数据分析
    以selenium模拟登陆12306
    PAT (Basic Level) Practice (中文)1076 Wifi密码 (15 分)
    PAT (Basic Level) Practice (中文)1047 编程团体赛 (20 分)
    PAT (Basic Level) Practice (中文)1029 旧键盘 (20 分)
    PAT (Basic Level) Practice (中文)1016 部分A+B (15 分)
    PAT (Basic Level) Practice (中文)1031 查验身份证 (15 分)
    PAT (Basic Level) Practice (中文)1041 考试座位号 (15 分)
    PAT (Basic Level) Practice (中文)1037 在霍格沃茨找零钱 (20 分)
  • 原文地址:https://www.cnblogs.com/weiweixiang/p/5667355.html
Copyright © 2011-2022 走看看