如果你仅仅只有Asp.net Web Forms背景转而学习Asp.net MVC的,我想你的第一个经历或许是那些曾经让你的编程变得愉悦无比的服务端控件都驾鹤西去了.FileUpload就是其中一个,而这个控件的缺席给我们带来一些小问题。这篇文章主要说如何在Asp.net MVC中上传文件,然后如何再从服务器中把上传过的文件下载下来.

在Web Forms中,当你把一个FileUpload控件拖到设计器中,你或许没有注意到在生成的HTML中会在form标签中加入一条额外属性enctype="multipart/form-data". 而FileUpload控件本身会生成为<input type=”file” />,在MVC的view里,有许多种方法可以做到同样效果,第一种的HTML如下:

  1. <form action="/" method="post" enctype="multipart/form-data">
  2. <input type="file" name="FileUpload1" /><br />
  3. <input type="submit" name="Submit" id="Submit" value="Upload" />
  4. </form>

注意form标签已经包括了enctype标签,而method属性则设为”post”,这样设置并不多于因为默认的提交时通过HTTP get方式进行的。下面这种方式,使用Html.BeginForm()扩展方法,会生成和上面同样的HTML:

  1. <%
  2. using (Html.BeginForm("", "home", FormMethod.Post, new {enctype="multipart/form-data"}))
  3. {%>
  4. <input type="file" name="FileUpload1" /><br />
  5. <input type="submit" name="Submit" id="Submit" value="Upload" />
  6. <% }%>

注意<input type=”file”>标签的name属性,我们在后面再讨论 
OK,现在我们可以浏览本地文件然后通过Upload提交按钮将文件提交到服务器端,下一步就是在服务器端处理上传的文件,在使用fileUpload控件时,你可以很轻松的通过FileUpload的hasFile方法来查看文件是否被上传。但是在Asp.net MVC中貌似就不是这么方便了,你会和原始的HTTP更接近一些,然而,一个扩展方法可以处理这些:

  1. public static bool HasFile(this HttpPostedFileBase file)
  2. {
  3. return (file != null && file.ContentLength > 0) ? true : false;
  4. }

当你看到对应的Controller类的代码时,你会发现Request对象作为HttpRequestBase类型的一个属性存在。HttpReuqestBase其实是HTTP请求的一个封装,暴漏了很多属性,包括Files collection(其实是HttpFileCollectionBase的集合),在集合中的每一个元素都是HttpPostedFileBase的集合,扩展方法是用于确保上传的文件是否存在。实际上,这和FileUpload.HasFile()方法的工作原理一致。

在Controller Action中使用起来其实很容易:

  1. public class HomeController : Controller
  2. {
  3. public ActionResult Index()
  4. {
  5. foreach (string upload in Request.Files)
  6. {
  7. if (!Request.Files[upload].HasFile()) continue;
  8. string path = AppDomain.CurrentDomain.BaseDirectory + "uploads/";
  9. string filename = Path.GetFileName(Request.Files[upload].FileName);
  10. Request.Files[upload].SaveAs(Path.Combine(path, filename));
  11. }
  12. return View();
  13. }
  14. }

多文件上传

或许你已经比我更早的想到如何更好的将Request.Files作为一个集合使用。这意味着它不仅仅只能容纳一个文件,而能容纳多个,我们将上面的View改为如下:

  1. <%
  2. using (Html.BeginForm("", "home", FormMethod.Post, new {enctype="multipart/form-data"}))
  3. {%>
  4. <input type="file" name="FileUpload1" /><br />
  5. <input type="file" name="FileUpload2" /><br />
  6. <input type="file" name="FileUpload3" /><br />
  7. <input type="file" name="FileUpload4" /><br />
  8. <input type="file" name="FileUpload5" /><br />
  9. <input type="submit" name="Submit" id="Submit" value="Upload" />
  10. <% }%>

效果如下:

在Controller的代码中已经检查了是否所有的文件上传框中都有文件,所以即使对于多文件上传,我们也不再需要修改Controller的代码,注意每一个<input type=”file”>都有不同的name属性,如果你需要调用其中一个,比如说,你需要引用第三个输入框只需要使用:Request.Files["FileUpload3"].

存入数据库

在你冲我狂吼”关注点分离”之前,我想声明下面的代码仅仅用于作为说明功能.我将ADO.Net的代码放入Controller action中,但我们都知道,这并不好。数据访问的代码应该放在Model中某个部分的数据访问层中.但是,下面这段代码仅仅可以给大家怎样将上传的文件存入数据库中一个更直观的印象,首先,我们需要创建一个数据表(FileTest)并创建一个表:FileStore

CREATE TABLE [dbo].[FileStore]( 
[ID] [int] IDENTITY(1,1) NOT NULL, 
[FileContent] [image] NOT NULL, 
[MimeType] [nvarchar](50) NOT NULL, 
[FileName] [nvarchar](50) NOT NULL 
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

FileContent域是image数据类型,用于存储以二进制数据形成的文件,而Index Action改为:

  1. public ActionResult Index()
  2. {
  3. foreach (string upload in Request.Files)
  4. {
  5. if (!Request.Files[upload].HasFile()) continue;
  6. string mimeType = Request.Files[upload].ContentType;
  7. Stream fileStream = Request.Files[upload].InputStream;
  8. string fileName = Path.GetFileName(Request.Files[upload].FileName);
  9. int fileLength = Request.Files[upload].ContentLength;
  10. byte[] fileData = new byte[fileLength];
  11. fileStream.Read(fileData, 0, fileLength);
  12. const string connect = @"Server=.\SQLExpress;Database=FileTest;Trusted_Connection=True;";
  13. using (var conn = new SqlConnection(connect))
  14. {
  15. var qry = "INSERT INTO FileStore (FileContent, MimeType, FileName) VALUES (@FileContent, @MimeType, @FileName)";
  16. var cmd = new SqlCommand(qry, conn);
  17. cmd.Parameters.AddWithValue("@FileContent", fileData);
  18. cmd.Parameters.AddWithValue("@MimeType", mimeType);
  19. cmd.Parameters.AddWithValue("@FileName", fileName);
  20. conn.Open();
  21. cmd.ExecuteNonQuery();
  22. }
  23. }
  24. return View();
  25. }

修改后的代码会以循环的方式遍历Web页面中所有的上传文件,并检查<input type=”file”>中是否已经加入文件,然后,从文件中提取出3个信息:文件名,MIME类型(文件的类型),HTTP Request中的二进制流。二进制数据被转换为byte数组,并以image数据类型存入数据库。MIME类型和文件名对于用户从数据库中提取文件来说非常重要。

将数据库中的文件返回给用户:

你如何将文件传送给用户取决于你最开始如何存储它,如果你将文件存入数据库,你会用流的方式将文件返还给用户,如果你将文件存在硬盘中,你只需要提供一个超链接即可,或者也可以以流的方式。每当你需要以流的方式将文件送到浏览器中,你都的使用到File()方法的重载(而不是使用我们先前一直使用的View()方法),对于File()方法有3类返回类型:FilePathResult,FileContentResult和FileStreamResult,第一种类型用于直接从磁盘返回文件;第二种类型用于将byte数组返回客户端;而第三种方式将已经生成并打开的流对象的内容返回客户端。

如果你还记得的话,我们将上传的文件存入了数据库,并以byte数组的形式存入FileContent域内.而当需要提取时,它仍然会以一个byte数组进行提取,这意味着我们使用返回FileContentResult的File()重载,如果我们想让提取的文件名更有意义,我们使用接受3个参数的重载,三个参数是:byte数组,MIME类型,文件名:

  1. public FileContentResult GetFile(int id)
  2. {
  3. SqlDataReader rdr; byte[] fileContent = null;
  4. string mimeType = "";string fileName = "";
  5. const string connect = @"Server=.\SQLExpress;Database=FileTest;Trusted_Connection=True;";
  6. using (var conn = new SqlConnection(connect))
  7. {
  8. var qry = "SELECT FileContent, MimeType, FileName FROM FileStore WHERE ID = @ID";
  9. var cmd = new SqlCommand(qry, conn);
  10. cmd.Parameters.AddWithValue("@ID", id);
  11. conn.Open();
  12. rdr = cmd.ExecuteReader();
  13. if (rdr.HasRows)
  14. {
  15. rdr.Read();
  16. fileContent = (byte[])rdr["FileContent"];
  17. mimeType = rdr["MimeType"].ToString();
  18. fileName = rdr["FileName"].ToString();
  19. }
  20. }
  21. return File(fileContent, mimeType, fileName);
  22. }

在View中最简单的使用来使用这个Action只需提供一个超链接:

<a href="/GetFile/1">Click to get file</a> 
如果在数据库中存储的图片是图片类型,和使用超链接不同的是,我们通过指向Controller action的一个带有src属性的<image>标签来获取:

<img src="/GetFile/1" alt="My Image" /> 
下面再让我们来看看使用FilePathResult(用于从硬盘提取文件)是多简单的事:

  1. public FilePathResult GetFileFromDisk()
  2. {
  3. string path = AppDomain.CurrentDomain.BaseDirectory + "uploads/";
  4. string fileName = "test.txt";
  5. return File(path + fileName, "text/plain", "test.txt");
  6. }

而这也可以用过超链接提取:

<a href="/GetFileFromDisk">Click to get file</a> 
而最后一个选择FileStreamResult也可以从磁盘中提取文件:

  1. public FileStreamResult StreamFileFromDisk()
  2. {
  3. string path = AppDomain.CurrentDomain.BaseDirectory + "uploads/";
  4. string fileName = "test.txt";
  5. return File(new FileStream(path + fileName, FileMode.Open), "text/plain", fileName);
  6. }

FilePathResult和FileStreamResult的区别是什么?我们又该如何取舍呢?主要的区别是FilePathResult使用HttpResponse.TransmitFile来将文件写入Http输出流。这个方法并不会在服务器内存中进行缓冲,所以这对于发送大文件是一个不错的选择。他们的区别很像DataReader和DataSet的区别。于此同时, TransmitFile还有一个bug,这可能导致文件传到客户端一半就停了,甚至无法传送。而FileStreamResult在这方面就很棒了。比如说:返回Asp.net Chart 控件在内存中生成的图表图片,而这并不需要将图片存到磁盘中.

Asp.net MVC 处理文件的上传下载的更多相关文章

  1. 利用Asp.net MVC处理文件的上传下载

    如果你仅仅只有Asp.net Web Forms背景转而学习Asp.net MVC的,我想你的第一个经历或许是那些曾经让你的编程变得愉悦无比的服务端控件都驾鹤西去了.FileUpload就是其中一个, ...

  2. ASP.NET MVC实现Excel文件的上传下载

    在应用系统开发当中,文件的上传和下载是非常普遍的需求.在基于.NET的C/S架构的项目开发当中,有多种方案可以实现文件的上传和下载(httpwebrequest.webclient等),而且多采用异步 ...

  3. Spring MVC 实现文件的上传和下载

    前些天一位江苏经贸的学弟跟我留言问了我这样一个问题:“用什么技术来实现一般网页上文件的上传和下载?是框架还是Java中的IO流”.我回复他说:“使用Spring MVC框架可以做到这一点,因为Spri ...

  4. Spring实现文件的上传下载

    背景:之前一直做的是数据库的增删改查工作,对于文件的上传下载比较排斥,今天研究了下具体的实现,发现其实是很简单.此处不仅要实现单文件的上传,还要实现多文件的上传. 单文件的下载知道了,多文件的下载呢? ...

  5. 在Window的IIS中创建FTP的Site并用C#进行文件的上传下载

    文件传输协议 (FTP) 是一个标准协议,可用来通过 Internet 将文件从一台计算机移到另一台计算机. 这些文件存储在运行 FTP 服务器软件的服务器计算机上. 然后,远程计算机可以使用 FTP ...

  6. 创建FTP的Site并用C#进行文件的上传下载

    创建FTP的Site并用C#进行文件的上传下载 文件传输协议 (FTP) 是一个标准协议,可用来通过 Internet 将文件从一台计算机移到另一台计算机. 这些文件存储在运行 FTP 服务器软件的服 ...

  7. linux链接及文件互相上传下载

    若排版紊乱可查看我的个人博客原文地址 基本操作 本篇博客主要介绍如何去链接远程的linux主机及如何实现本地与远程主机之间文件的上传下载操作,下面的linux系统是CentOS6.6 链接远程linu ...

  8. SocketIo+SpringMvc实现文件的上传下载

    SocketIo+SpringMvc实现文件的上传下载 socketIo不仅可以用来做聊天工具,也可以实现局域网(当然你如果有外网也可用外网)内实现文件的上传和下载,下面是代码的效果演示: GIT地址 ...

  9. JAVAWEB之文件的上传下载

    文件上传下载 文件上传: 本篇文章使用的文件上传的例子使用的都是原生技术,servelt+jdbc+fileupload插件,这也是笔者的习惯,当接触到某些从未接触过的东西时,总是喜欢用最原始的东西将 ...

随机推荐

  1. Java学习笔记之:Java的数据类型

    一.介绍 变量就是申请内存来存储值.也就是说,当创建变量的时候,需要在内存中申请空间. 内存管理系统根据变量的类型为变量分配存储空间,分配的空间只能用来储存该类型数据. Java语言提供了八种基本类型 ...

  2. Go语言博客

    http://www.cnblogs.com/concurrency/p/4293613.html#3130523

  3. 39. Combination Sum

    题目: Given a set of candidate numbers (C) and a target number (T), find all unique combinations in C  ...

  4. 如何将phantomjs单独部署在服务端

    如何将phantomjs单独部署在服务端 文章目录 一. 容我分析(lao dao)几句 二. 服务端 Look here 服务端phantomjs搭建 web端搭建及如何调用phantomjs 三. ...

  5. Java API —— IO流( FileInputStream & FileOutputStream & BufferedInputStream & BufferedOutputStream )

    1.IO流概述 · IO流用来处理设备之间的数据传输        · 上传文件和下载文件        · Java对数据的操作是通过流的方式 · Java用于操作流的对象都在IO包中   2.IO ...

  6. SetCapture、ReleaseCapture、GetCapture

    正常情况下,鼠标指针位于哪个窗口区域内,鼠标消息就自动发给哪个窗口.如果调用了SetCapture,之后无论鼠标的位置在哪,鼠标消息都发给指定的这个窗口,直到调用ReleaseCapture或者调用S ...

  7. Java知识点:instanceof关键字

    介绍 格式:a instanceof B,其中a是一个variable(设a所指向的对象的类型命名为A,即A是一个type. 功能:判断A是否 is-a B. 当a=null时,始终返回false. ...

  8. javascript 命名空间的实例应用

    /** * 创建全局对象MYAPP * @module MYAPP * @title MYAPP Global */ var MYAPP = MYAPP || {}; /** * 返回指定的命名空间, ...

  9. delphi 转换sql server 中的 bit类型

    FieldByName('e').AsBoolean = false 其中e为 sql server 中的bit类型.

  10. ubuntu鼠标突然不能使用的解决方法

    今天发现鼠标(usb即插即用)不能用了,最后发现需要接通充电才可以!!!用电池的时候居然不可以用鼠标?