说完了WebForm和MVC中的文件上传,就不得不说用户从服务器端下载资源了。那么今天就扯扯在WebForm和MVC中是如何实现文件下载的。说起WebForm中的文件上传,codeshark在他的博文ASP.NET实现文件下载中讲到了ASP.NET中文件下载的4种方式。当然文章主要指的是在WebForm中的实现方式。总结得相当到位,那么这篇,就先来看看MVC中文件下载的方式。然后再回过头来看看它们实现方式的关联。

Part 1 MVC中的文件下载

在mvc中微软封装好了ActionResult的诸多的实现,这使我们根据需要灵活地选择操作结果来响应用户的操作。这其中一个很重要的实现就是.NET Framework 4.0中的FileResult。表示一个用于将二进制文件内容发送到响应的基类。FileResult类有三个实现:FileContentResult、FileStreamResult、FilePathResult。mvc中的文件下载就是依赖这三个子类。不扯闲,先来看具体实现。

 FileContentResult

public ActionResult FileDownLoad()
{
FileStream fs = new FileStream(Server.MapPath("~/uploads/Desert.jpg"), FileMode.Open, FileAccess.Read);
byte[] bytes = new byte[fs.Length];
fs.Read(bytes, 0, Convert.ToInt32(bytes.Length));
return File(bytes, "image/jpeg", "Desert.jpg");
}

 FileStreamResult

public ActionResult FileDownLoad()
{
return File(new FileStream(Server.MapPath("~/uploads/Desert.jpg"), FileMode.Open, FileAccess.Read), "image/jpeg", "Desert.jpg");
}

 FilePathResult

public ActionResult FileDownLoad()
{
return File(Server.MapPath("~/uploads/Desert.jpg"), "image/jpeg", "Desert.jpg");

看了上面的代码是不是感觉这似乎比codeshark文章中讲到的WebForm中的文件下载代码更简洁了?确实!那么我们同时也不禁要问:不是说mvc中的文件下载依赖于FileContentResult、FilePathResult、FileStreamResult吗,为什么这里边无一例外都是return File(......)呢?这个对于接触过mvc的你来说,相信难不倒你,F12一下就全明白了:

(图1-1)

原来File(......)方法的背后返回的是FileContentResult、FileStreamResult、FilePathResult的实例。这就不足为怪了。那么也就是说其实上面的3钟实现方式你完全可以改成如下形式:

//FilePathResult
return new FilePathResult(Server.MapPath("~/uploads/Desert.jpg"), "image/jpeg") { FileDownloadName = "Desert.jpg" };
//FileStreamResult
return new FileStreamResult(new FileStream(Server.MapPath("~/uploads/Desert.jpg"), FileMode.Open, FileAccess.Read), "image/jpeg") { FileDownloadName = "Desert.jpg" };
//FileContentResult
return new FileContentResult(System.IO.File.ReadAllBytes(Server.MapPath("~/uploads/Desert.jpg")), "image/jpeg") { FileDownloadName = "Desert.jpg" };

如果你通过ILSpay查看源码的话,你会发现其实对应的return File(......)的内部实现就是如此。

问题1:不给fileDownloadNane赋值效果会如何

回过头来再看下图1-1,我们还会发现FileContentResult、FileStreamResult、FilePathResult的下载方法,还各自对应的存在一个没有第三个参数fileDownloadNane的方法重载。那么这个方法又是用来干嘛呢?小段代码看下便知:

public ActionResult FileDownLoad()
{
return File(Server.MapPath("~/uploads/Desert.jpg")
}

--------------------------------------------------------------------------------运行结果-------------------------------------------------------------------------------------------------------------------

通过运行结果截图一眼就能看出,此时将图片直接输出在页面上,这实现图片的显示功能。那么同时我们也能够知道,同样的这样也是可以的:

return new FilePathResult(Server.MapPath("~/uploads/Desert.jpg"), "image/jpeg") ;

问题2:加不加fileDownloadNane背后到底做了什么

当然其它两种方式同样如此。那么我们不禁要问:为什么给不给fileDownloadName赋值,实现的效果完全不一样呢?那么你肯定想到了在内部肯定做了什么来加以区分。那么就以FilePathResult为例,用ILSpy来看看究竟:

(图1-2)

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

(图1-3)

先看图1-2在FileResult类中有一个FileDownloadName公开属性,我们知道ActionResult类最终要执行子类的ExecuteResult方法,而在FileResultExecuteResult方法中,对FileDownloadName进行了非空判断。如果不为空则通过Response.AddHeader()方法向客户端浏览器发送文件。如果为空则调用子类FilePathResult的重写方法WriteFile() (图1-3所示)直接向页面输出响应流。当然FileContentResult、FileStreamResult道理相同。

问题3:FileContentResult、FileStreamResult、FilePathResult文件下载的方式到底有什么不同

嗯,要回答这个问题,不用说,还是得看看它的内部实现。那么我们就一个个地来看看究竟。

图1-2和图1-3种已经看得很清楚,最终文件下载时指定了FileResultExecuteResult方法,而在ExecuteResult方法中调用了FileResult的子类的WriteFile方法。那么我只需要查看每个子类的WriteFile方法便知道了。

FileContentResult

FileStreamResult

FilePathResult

再去看看codeshark的文章ASP.NET实现文件下载,在这篇文章中他总结了文件下载的4种方式:

方式一:TransmitFile实现下载。将指定的文件直接写入 HTTP 响应输出流,而不在内存中缓冲该文件。

方式二:WriteFile实现下载,将指定的文件直接写入 HTTP 响应输出流。注意:对大型文件使用此方法时,调用此方法可能导致异常。可以使用此方法的文件大小取决于 Web 服务器的硬件配置。

方式三:WriteFile分块下载

方式四:Response.BinaryWrite()流方式下载

那么与mvc中文件下载对照一下,不难发现,FilePathResult实际上是采用的Response.TransmitFile的方式实现下载的;而FileStreamResult是采用的WriteFile分块下载;FileContentResult在文章中没有直接的对照方式,而它采用的是Response.OutputStream.Write的方式。另外Response.BinaryWrite()流的方式在mvc中没有实现。当然在msdn上给出了实现我直接贴出实现代码:

实现代码:

public class BinaryContentResult : ActionResult
{
public BinaryContentResult()
{
} // Properties for encapsulating http headers.
public string ContentType { get; set; }
public string FileName { get; set; }
public byte[] Content { get; set; } // The code sets the http headers and outputs the file content.
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.ClearContent();
context.HttpContext.Response.ContentType = ContentType; context.HttpContext.Response.AddHeader("content-disposition",
"attachment; filename=" + FileName); context.HttpContext.Response.BinaryWrite(Content);
context.HttpContext.Response.End();
}
}

调用代码:

public ActionResult Download(string fn)
{
// Check whether the requested file is valid.
string pfn = Server.MapPath("~/App_Data/download/" + fn);
if (!System.IO.File.Exists(pfn))
{
throw new ArgumentException("Invalid file name or file not exists!");
} // Use BinaryContentResult to encapsulate the file content and return it.
return new BinaryContentResult()
{
FileName = fn,
ContentType = "application/octet-stream",
Content = System.IO.File.ReadAllBytes(pfn)
};
}

问题4:那么FileContentResult的Response.OutputStream.Write()和Response.WriteFile()又有什么区别昵

关于这个问题,我查了很久,没有得到比较满翼的答案,在这里希望大神指点一二!

Part 2 WebForm中的文件下载

看看这篇文章ASP.NET实现文件下载一目了然。四种方式不多说了。到这里,不得不说MVC中的文件下载is so easy!这还得归功于微软的封装。那么问题来了,在WebForm中我们是不是因该也封装一个这样的实现,这样在以后使用的时候不用写(当然一般是copy)重复写这么写这些容易忘记的代码了昵? Of course, just do it! 网上我没有找到一个针对这个实现的封装(有大神代码可以贡献一下)。我索性自己写一个,好不好,就不说了。

1.定义抽象类

先定义一个抽象列FileDownloader,定义公共的下载行为WriteFile方法和执执行下载的Execute方法以及文件下载名_fileDownloadName。代码如下:

public abstract class FileDownloader
{
private string _fileDownloadName;
public FileDownloader(string contentType)
{
if (string.IsNullOrEmpty(contentType))
{
throw new ArgumentException("contentType");
}
this.ContentType = contentType;
}
public void Execute(HttpContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
HttpResponse response = context.Response;
response.ClearContent();
response.ContentType = this.ContentType;
if (!string.IsNullOrEmpty(this.FileDownloadName))
{
context.Response.AddHeader("content-disposition",
"attachment; filename=" + this.FileDownloadName);
}
this.WriteFile(context.Response);
response.Flush();
response.End();
}
protected abstract void WriteFile(HttpResponse response);
public string ContentType { get; private set; }
public string FileDownloadName
{
get
{
return (this._fileDownloadName ?? string.Empty);
}
set
{
this._fileDownloadName = value;
}
}
}

2.创建实现类

此处模拟MVC中的实现方式创建对应的WebForm中的实现方式,为了易于区分说明,我采取和MVC中同样的类名。

MVC中FileContentResult的下载方式(区别于FileStreamResult分区下载),这里实现一下:

public class FileContentResult : FileDownloader
{
public FileContentResult(byte[] fileContents, string contentType)
: base(contentType)
{
if (fileContents == null)
{
throw new ArgumentNullException("fileContents");
}
this.FileContents = fileContents;
}
public byte[] FileContents { get; private set; }
protected override void WriteFile(HttpResponse response)
{
response.OutputStream.Write(this.FileContents, 0, this.FileContents.Length);
}
}

FileStreamResult(分区下载):

public class FileStreamResult : FileDownloader
{
public FileStreamResult(Stream fileStream, string contentType)
: base(contentType)
{
if (fileStream == null)
{
throw new ArgumentNullException("fileStream");
}
this.FileStream = fileStream;
}
public Stream FileStream { get; private set; }
protected override void WriteFile(HttpResponse response)
{
Stream outputStream = response.OutputStream;
using (this.FileStream)
{
byte[] buffer = new byte[4096];
while (true)
{
int num = this.FileStream.Read(buffer, 0, 4096);
if (num == 0)
{
break;
}
outputStream.Write(buffer, 0, num);
}
}
}
}

FIlePathResult:

public class FIlePathResult : FileDownloader
{
public FIlePathResult(string fileName, string contentType)
: base(contentType)
{
if (string.IsNullOrEmpty(fileName))
{
throw new ArgumentNullException("fileName");
}
this.FileName = fileName;
}
public string FileName { get; private set; } protected override void WriteFile(HttpResponse response)
{
response.TransmitFile(this.FileName);
}
}

另外上面我们也采用了微软上BinaryContentResult的实现,这里同样也实现一下BinaryContentResult:

public class BinaryContentResult : FileDownloader
{
public BinaryContentResult(byte[] fileContents, string contentType)
: base(contentType)
{
if (fileContents == null)
{
throw new ArgumentNullException("fileContents");
}
this.FileContents = fileContents;
} protected override void WriteFile(HttpResponse response)
{
response.BinaryWrite(FileContents);
} public byte[] FileContents { get; private set; }
}

另外还有HttpResponse.WriteFile(net 2.0中的提出的,对大型文件使用此方法时,调用此方法可能会引发异常这里姑且称之为旧的实现方式:

public class FilePathOldResult : FileDownloader
{
public FilePathOldResult(string fileName, string contentType)
: base(contentType)
{
if (string.IsNullOrEmpty(fileName))
{
throw new ArgumentNullException("fileName");
}
this.FileName = fileName;
}
public string FileName { get; private set; }
protected override void WriteFile(HttpResponse response)
{
response.WriteFile(this.FileName);
}

Part 3 问题开发

在文件下载中,我们可能需要自动获取文件的MIME类型,这在指定文件的下载类型时。那么如何获取文件的MIME类型呢?在Mitchell Chu的博客.NET获取文件的MIME类型(Content Type)中给出了比较好的答案。这里就不做赘述。

Part 4 The end

回过头来看还是那句话,因为MVC和WebForm都是基于ASP.NET框架,因此文件下载功能的背后还是采用了相同的组件实现。

注:由于个人技术有限,对某些概念的理解可能会存在偏差,如果你发现本文存在什么bug,请指正。谢谢!

完。

MVC&WebForm对照学习:文件下载的更多相关文章

  1. MVC&WebForm对照学习:文件上传(以图片为例)

    原文  http://www.tuicool.com/articles/myM7fe 主题 HTMLMVC模式Asp.net 博客园::首页::  ::  ::  ::管理 5 Posts :: 0 ...

  2. MVC&WebForm对照学习:ajax异步请求

    写在前面:由于工作需要,本人刚接触asp.net mvc,虽然webform的项目干过几个.但是也不是很精通.抛开asp.net webform和asp.net mvc的各自优劣和诸多差异先不说.我认 ...

  3. MVC&WebForm对照学习:传值方式

    刚从webform开发转到mvc,如果说像路由这样稍微复杂一点的知识点还可以暂时先放一放(前提是默认的路由规则基本满足大部分需求),那有个问题在快速开发中,我想是必须要当即解决的,那就是webform ...

  4. MVC系列——MVC源码学习:打造自己的MVC框架(一:核心原理)

    前言:最近一段时间在学习MVC源码,说实话,研读源码真是一个痛苦的过程,好多晦涩的语法搞得人晕晕乎乎.这两天算是理解了一小部分,这里先记录下来,也给需要的园友一个参考,奈何博主技术有限,如有理解不妥之 ...

  5. [转]MVC系列——MVC源码学习:打造自己的MVC框架(一:核心原理)

    本文转自:http://www.cnblogs.com/landeanfen/p/5989092.html 阅读目录 一.MVC原理解析 1.MVC原理 二.HttpHandler 1.HttpHan ...

  6. MVC系列——MVC源码学习:打造自己的MVC框架(四:了解神奇的视图引擎)

    前言:通过之前的三篇介绍,我们基本上完成了从请求发出到路由匹配.再到控制器的激活,再到Action的执行这些个过程.今天还是趁热打铁,将我们的View也来完善下,也让整个系列相对完整,博主不希望烂尾. ...

  7. MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)

    前言:上篇介绍了下自己的MVC框架前两个版本,经过两天的整理,版本三基本已经完成,今天还是发出来供大家参考和学习.虽然微软的Routing功能已经非常强大,完全没有必要再“重复造轮子”了,但博主还是觉 ...

  8. MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)

    前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...

  9. MVC缓存OutPutCache学习笔记 (二) 缓存及时化VaryByCustom

    <MVC缓存OutPutCache学习笔记 (一) 参数配置> 本篇来介绍如何使用 VaryByCustom参数来实现缓存的及时化.. 根据数据改变来及时使客户端缓存过期并更新.. 首先更 ...

随机推荐

  1. css块级元素、行内元素

    说说对html页面元素的排列认识: html中所有元素从上到下排列,所以需要css来对其中的元素进行排序.调节样式,并用js为其添加交互效果. css的排序.定位是相对块级元素而言的,margin/p ...

  2. gcd,lcm,ext_gcd,inv

    Least Common Multiple http://acm.hdu.edu.cn/showproblem.php?pid=1019 #include<cstdio> int gcd( ...

  3. IIS7.5 自定义Html/shtml/htm...后缀映射

    以添加html后缀的文件的 映射为例: 1.打开iis管理器,点击 2.点击打开处理程序映射 3.添加托管处理程序映射 4.请求路径 *.html 类型: System.Web.UI.PageHand ...

  4. PAT-乙级-1054. 求平均值 (20)

    1054. 求平均值 (20) 时间限制 400 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 CHEN, Yue 本题的基本要求非常简单:给定N个实 ...

  5. 个人Android作品开发——FinancePad记账通

    开发背景:针对在外工作的年轻一族,记录平时生活消费记录,方便清楚自己的钱花在哪些地方,方便管理. 开发时间:2013年7月中旬 开发环境:Eclipse Andorid SDK V2.0 开发语言:J ...

  6. linux源代码阅读笔记 linux文件系统(转)

    linux文件系统:   操作系统的文件数据除了文件实际内容外,还有非常多的属性,如文件权限(rwx)与文件属性(所有者.群组.时间参数等).   文件系统通常将这两部分数据存放在不同的块.权限属性放 ...

  7. 谁会是 Zabbix 和 Nagios 的继任者?

    [编者按]本文根据 Dataloop.IO 的创始人兼 CEO David Gildeh 对监控工具市场的现状分析以及对未来发展趋势的展望,展开拓展讨论. 为什么监控还是一塌糊涂? 为了调研市场,从而 ...

  8. 关于dynamic_cast

    http://www.groad.net/bbs/read.php?tid-5476.html dynamic_cast 进行运行时强制转换.如果在执行时转换非法,则会转换失败.dynamic_cas ...

  9. windows下安装ubantu

        首先声明我是一个linux大菜鸟,之所以学这个,一个是好玩,另外做DL的一些软件如Caffe要在这个平台上运行,所以没事就鼓捣鼓捣.linux是一种内核,市场上支持这种内核的操作系统有uban ...

  10. ibatis缓存初探(1)

    一,IBATIS 缓存机制使用 1,sqlMapConfig.xml中配置   1.SqlMapConfig.xml中 <settings cacheModelsEnabled="tr ...