摘要:上篇文章写到一种上传图片的方法,其中提到那种方法的局限性,就是上传的文件只能保存在本项目目录下,在其他目录中访问不到该文件。这与浏览器的安全性机制有关,浏览器不允许用户用任意的路径访问服务器上的资源,因为这可能造成服务器上其他位置的信息被泄露。浏览器只允许用户用相对路径直接访问本项目路径下的资源。那么,如果A项目要访问B项目上传的文件资源,这就产生问题了。所以这就需要另外一种方法来解决这个问题,那就是通过 流(Stream)的形式上传和下载文件资源。这种方法因为不是通过路径直接访问文件,而是先把文件读取的流中,然后将流中的数据写入到新的文件中,还原需要上传的文件,所以也就不存在上面的问题了。本片博客,着重介绍一下这种方式的实现。

一、准备工作

首先,还是做一下准备工作:

(1)创建一个解决方案(图片上传),一个mvc项目(Console);

(2)然后新建控制器(UploadImageController.cs);

如图:

我这个demo是在一个code first实现案例上写的,所以你看到这个解决方案还有其他几个项目在里面,但是不用担心,本案例只涉及mvc项目(Console),不与其他几个项目产生依赖。

(3)引入layui相关的依赖,编写前端代码:

本案例中前台页面使用的是layui,所以提前引入layui的依赖,然后写好页面的代码(该代码自layui网站上copy),如下:

html:

<link href="~/Content/layui/css/layui.css" rel="stylesheet" />
<script src="~/Scripts/jquery-3.3.1.min.js"></script>
<script src="~/Content/layui/layui.js"></script>
<script src="~/Content/layui/layui.all.js"></script> <div class="layui-upload" style="margin-top:100px;">
<button type="button" class="layui-btn" id="test1">上传图片</button>
<div class="layui-upload-list">
<img class="layui-upload-img" id="demo1" style="width:100px;height:auto;">
<p id="demoText"></p>
</div>
</div>

js:

<script type="text/javascript">
layui.use('upload', function(){
var $ = layui.jquery, upload = layui.upload;
//普通图片上传
var uploadInst = upload.render({
elem: '#test1',
url: '@Url.Action("Upload", "UploadImage")'
,before: function(obj){
//预读本地文件示例,不支持ie8
obj.preview(function(index, file, result){
$('#demo1').attr('src', result); //图片链接(base64)
});
}
,done: function(res){
//如果上传失败
alert(JSON.stringify(res));
// return layer.msg("上传成功");
//上传成功
}
,error: function(){
//演示失败状态,并实现重传
var demoText = $('#demoText');
demoText.html('<span style="color: #FF5722;">上传失败</span> <a class="layui-btn layui-btn-xs demo-reload">重试</a>');
demoText.find('.demo-reload').on('click', function(){
uploadInst.upload();
});
}
});
});
</script>

以上代码为layui的图片上传示例代码,可到layui 文件上传部分获取。

上面的代码中,只需把url处的链接换成后台的图片上传方法即可。

如图所示:

就一个按钮,上面和下面的内容都是母版页里自带的。

二、上传功能实现

 1.简述流上传文件的过程

在使用流上传文件时,最好通过阅读书籍,对相关的知识有一定的了解。使用流上传文件与直接上传文件相比,过程更复杂,这其实相当于把一个文件 由整拆为零,传输到对应位置后再 由零重建为整 的一个过程。

关于流的使用中,有几个点需要了解:

(1)路径:path,这是文件会被保存的地方,通常会使用  Path.Conbine(path1,path2). 将路径和文件名组合为一个完整的路径,如下:

 string filePath = Path.Combine(@"D:\Asp.Net\C#code\C#基础补习\Upload",fileName);

(2)缓存数组:buffer,这是一个字节类型的数组,输入流中的数据会被依次存储到缓存数组中,然后缓存数组把其中的数据写到新的流(输出流)中;

byte[] buffer;

(3)FileStream:文件流,这个类主要用于在二进制文件中  “读” 和 “写” 二进制数据。上图中流读取文件和写入文件都是过这个类来实现的。下面给出几条示例:

 var inputStream = new FileStream(inputfile,FileMode.Open,FileAccess.Read,FileShare.Read);

上一句 创建一个文件流的对象,这个对象有几个参数,用于控制这个流来进行什么样的操作:

inputfile:这是一个文件路径,表示把这个路径指定的二进制文件读入到流中。如:

 var inputStream = new FileStream(@“D:\Asp.Net\C#code\C#基础补习\Upload\1.jpg”,FileMode.Open,FileAccess.Read);

就是把这个1.jpg读入到流中。

FileMode:指定系统打开选定的文件的方式,有以下几个选项(枚举值):

  //
// 摘要:
// 指定操作系统打开文件的方式。
[ComVisible(true)]
public enum FileMode
{
//
// 摘要:
// 指定操作系统应创建一个新的文件。 这要求 System.Security.Permissions.FileIOPermissionAccess.Write
// 权限。 如果该文件已存在, System.IO.IOException 则会引发异常。
CreateNew = ,
//
// 摘要:
// 指定操作系统应创建一个新的文件。 如果该文件已存在,则会覆盖它。 这要求 System.Security.Permissions.FileIOPermissionAccess.Write
// 权限。 FileMode.Create 等效于请求,如果该文件不存在,则使用 System.IO.FileMode.CreateNew; 否则为使用 System.IO.FileMode.Truncate。
// 如果该文件已存在但为隐藏的文件, System.UnauthorizedAccessException 则会引发异常。
Create = ,
//
// 摘要:
// 指定操作系统应打开现有文件。 若要打开该文件的能力是依赖于指定的值 System.IO.FileAccess 枚举。 一个 System.IO.FileNotFoundException
// 如果文件不存在将引发异常。
Open = ,
//
// 摘要:
// 指定操作系统应打开一个文件,是否它存在,则否则,应创建一个新的文件。 如果使用打开该文件 FileAccess.Read, ,System.Security.Permissions.FileIOPermissionAccess.Read
// 权限是必需的。 如果文件访问是 FileAccess.Write, ,System.Security.Permissions.FileIOPermissionAccess.Write
// 权限是必需的。 如果使用打开该文件 FileAccess.ReadWrite, ,这两个 System.Security.Permissions.FileIOPermissionAccess.Read
// 和 System.Security.Permissions.FileIOPermissionAccess.Write 权限是必需的。
OpenOrCreate = ,
//
// 摘要:
// 指定操作系统应打开现有文件。 当打开文件时,应被截断,以便其大小为零字节。 这要求 System.Security.Permissions.FileIOPermissionAccess.Write
// 权限。 尝试从文件中读取使用打开 FileMode.Truncate 导致 System.ArgumentException 异常。
Truncate = ,
//
// 摘要:
// 如果它存在,并且查找到该文件的末尾,或者创建一个新文件,请打开该文件。 这要求 System.Security.Permissions.FileIOPermissionAccess.Append
// 权限。 FileMode.Append 可以仅在结合使用 FileAccess.Write。 尝试查找该文件将引发结束之前将其置于 System.IO.IOException
// 异常,并且任何尝试读取失败,将引发 System.NotSupportedException 异常。
Append =
}

常用的几个项为:FileMode.Create /CreateNew/Open/OpenOrCreate,

其中Open表示这个流会打开这个文件,Create表示会在该路径下创建一个这个命名的文件,

FileMode和FileAccess共同控制流对文件进行操作的方式。

FileAccess:控制对该文件进行读或者写的权限,比如,你要上传一个文件,那么你首先要读取这个文件里的数据,那这个就要设置为 读 ,又比如,某个文件的数据已经读到缓存区了,需要把它存到指定的位置,那么这个时候,就要把数据写入一个新的文件,那么就要用写。这个也有几个选项(枚举值):

   // 摘要:
// 对于读、 写或读/写访问的文件中定义的常数。
[ComVisible(true)]
[Flags]
public enum FileAccess
{
//
// 摘要:
// 对文件的读取访问权限。 可以从文件读取数据。 将与结合起来 Write 为读/写访问。
Read = ,
//
// 摘要:
// 对文件的写入访问权限。 数据可以写入该文件。 将与结合起来 Read 为读/写访问。
Write = ,
//
// 摘要:
// 读取和写入到文件的访问。 可以写入和从文件中读取数据。
ReadWrite =
}

FileMode和FileAccess对应起来使用,一般Open和Read组合,Create和Write组合。

(4)偏移量 offset:流中的数据写入(或读出)到缓存数组中时,数据是按照类似排队的顺序,一个一个写的,流中有一个指针一样的东西,数据读了几个,这个指针就向前移动几位,指针移动的多少就是偏移量,偏移量作为流的使用中的一个重要的参数,在文件分段上传中作用明显。

2.上传功能的实现:

这里我直接给出代码,代码里有详细的解释,不再另作说明:

        public string Upload()
{
///获取上传的文件
var file = Request.Files[];
//获取上传文件的文件名
string fileName = file.FileName;
//上传路径
string filePath = Path.Combine(@"D:\Asp.Net\C#code\C#基础补习\Upload",fileName);
//定义缓存数组
byte[] buffer;
//将文件数据塞到流里
var inputStream = file.InputStream;
///获取读取数据的长度
int readLength = Convert.ToInt32(inputStream.Length);
///给缓存数组指定大小
buffer = new byte[readLength];
//设置指针的位置为 最开始 的位置
inputStream.Seek(,SeekOrigin.Begin);
//从位置 0 开始读取上传的文件的数据,数据读取到第一个参数buffer(缓存区)中
inputStream.Read(buffer,,readLength);
//创建输出文件流,指定文件的输出位置,模式为创建该新文件,读写权限为 写
using (var outputStream = new FileStream(filePath,FileMode.Create,FileAccess.Write))
{
//设置指针的位置为 最开始 的位置
outputStream.Seek(,SeekOrigin.Begin);
//从起始位置 将 第一个参数 buffer(缓存区)里的数据写入到 filePath 指定的文件中
outputStream.Write(buffer,,buffer.Length);
}
        //向前台返回上传文件的文件名,表示上传成功
return JsonConvert.SerializeObject(new { Name = fileName });
}

写好该文件后,将前端js中的 url 处写上指向该代码的链接, 然后运行,查看结果:如图所示:

然后,打开对应目录的文件夹,查看文件是否已上传:

 3.另一种写法,针对比较大的文件

上一种方法我们给定数组的大小是根据流的长度来确定的,因为这里是上传的图片,数据量不是很大,这样做没什么问题,但是上传的文件比较大的话,文件可能不会很顺利的上传。

这里提供另外一种上传方法,当然,还是用 流 上传 ,但不是定义一个 刚刚好的数组 ,一次性上传,而是定义一个固定大小的数组,每次取一定量的数据,然后把数据写到新文件中,再清空数组,之后又用数组去取定量的数据,再写入,在取数据,再写入,像这样循环往复,直至文件上传完毕为止。下面是这种方法的代码,同样有比较详细的注释,不再另作说明:

        /// <summary>
/// 文件上传
/// </summary>
/// <returns></returns>
public string UploadFile()
{
var file = Request.Files[];
int BUFFERSIZE = ;
var name = string.Empty;
// var uploadTime = DateTime.Now;
///文件上传的最底层目录路径 格式为 \文件id\文件名
var fileId = Guid.NewGuid();
name = file.FileName;
// var uploadPath = Path.Combine(fileId.ToString(), name);
///文件上传后的位置
var outputPath = Path.Combine(@"D:\Asp.Net\C#code\C#基础补习\Upload", name);
///将接收到的文件转化为流
var inputStream = file.InputStream;
///流数据读取到数组中的偏移量
long offset = ;
///获取或设置光标在当前流中的位置
inputStream.Position = offset;
///存储流中数据的数组
// byte[] buffer = new byte[BUFFERSIZE];
///读取流中的数据,读到数组中
while (offset < inputStream.Length)
{
///存储流中数据的数组,该数组大小根据流中未读取数据量大小调整,若未读取数据量大于规定的数组最大大小,则数组大小设为该数组的最大容量
byte[] buffer = new byte[Math.Min(BUFFERSIZE,inputStream.Length - offset)];
int nRead = inputStream.Read(buffer,,buffer.Length);
if (nRead < )
{
///若读取完毕,则跳出循环
break;
}
try
{
///将读取到数组中的数据写入新的文件中,再保存到指定的位置
using (var outputStream = new FileStream(outputPath, FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
outputStream.Seek(offset, SeekOrigin.Begin);//将流中的光标移到第一次读取的数据之后
outputStream.Write(buffer, , buffer.Length);
outputStream.Flush(); offset = outputStream.Length;
}
}
catch (Exception exception)
{ throw exception;
} }
return JsonConvert.SerializeObject(new { Id = fileId,Name = name});
}

同样,演示一下这种方法是否能成功:

先把url处改为 @Url.Action("UploadFile","UploadImage"),

,

效果如下:

三、下载文件

既然有文件上传,按必然就少不了文件下载,下面给出一个文件下载的功能实现。

首先,在前端页面添加一个 a标签按钮 和 一个图片链接 按钮,如下图所示:

<div>
<a href="@Url.Action("DownloadFile","UploadImage")">下载图片</a>
<img src="@Url.Action("DownloadFile","UploadImage")" alt="Alternate Text" style="width:100px;height:auto;"/>
</div>

其中DownloadFile是后台代码,然后给出后台代码,由于下载是上传的逆过程,所以这里不再做出详细解释:

       /// <summary>
/// 文件下载 ,该案例仅为一个文件下载的demo,其文件名和路径等信息,此处直接给出固定值,实际应用中可根据需求灵活给定文件名和路径
/// </summary>
/// <returns>返回文件</returns>
public ActionResult DownloadFile()
{
string fileName = "角楼.jpg";
byte[] buffer;
string contentType = "application/octec-stream";
///MemoryStream()内存流
using (var outputStream = new MemoryStream())
{
try
{ long offset = ;
string inputFilePath = Path.Combine(@"D:\Asp.Net\C#code\C#基础补习\Upload", fileName);
using (var inputStream = new FileStream(inputFilePath, FileMode.Open, FileAccess.Read))
{
long readLength = inputStream.Length;
buffer = new byte[readLength];
inputStream.Seek(offset,SeekOrigin.Begin);
inputStream.Read(buffer,,Convert.ToInt32(readLength));
}
}
catch (Exception exception)
{ throw exception;
}
outputStream.Write(buffer,,buffer.Length);
return File(outputStream.GetBuffer(),contentType,fileName); }
}

下面给出演示图片:

下载此图:

文件默认下载到电脑上的  “下载” ,文件夹。

关于文件.net mvc下另一种图片上传的方法就介绍到这里,本篇只着重介绍文件上传和下载的过程,实际应用中会有很多其他方面的点要涉及,这里不进行说明,如果时间允许,会再介绍。

本程序的源代码需要的同学可给留言 邮箱 ,我会第一时间发给你。

本人系5个月.net程序员 ,菜鸡一只,以上所述,如有重大谬误,大牛请狠批!

我的联系方式:eMail:3074596466@qq.com

祝大家小年快乐!

如有帮助,能不能点个推荐呢,哈哈哈!有点恬不知耻哈,不要介意!

.net mvc + layui做图片上传(二)—— 使用流上传和下载图片的更多相关文章

  1. 文件上传二:FormData上传

    介绍三种上传方式: 文件上传一:伪刷新上传 文件上传二:FormData上传 文件上传三:base64编码上传 Flash的方式也玩过,现在不推荐用了. 真正的异步上传,FormData的更多操作,请 ...

  2. .net mvc + layui做图片上传(一)

    图片上传和展示是互联网应用中比较常见的一个功能,最近做的一个门户网站项目就有多个需要上传图片的功能模块.关于这部分内容,本来功能不复杂,但后面做起来却还是出现了一些波折.因为缺乏经验,对几种图片上传的 ...

  3. cropper.js 二次开发:截图并下载图片

    cropper.js 是一个基于jquery的图片截取库. 参考:https://blog.csdn.net/weixin_38023551/article/details/78792400 我的代码 ...

  4. ASP.NET MVC+LayUI视频上传

    前言: 前段时间在使用APS.NET MVC+LayUI做视频上传功能的时,发现当上传一些内存比较大的视频就会提示上传失败,后来通过查阅相关资料发现.NET MVC框架为考虑安全问题,在运行时对请求的 ...

  5. SpringBoot图片上传(四) 一个input上传N张图,支持各种类型

    简单介绍:需求上让实现,图片上传,并且可以一次上传9张图,图片格式还有要求,网上找了一个测试了下,好用,不过也得改,仅仅是实现了功能,其他不尽合理的地方,还需自己打磨. 代码: //html<d ...

  6. SpringMVC单文件上传、多文件上传、文件列表显示、文件下载(转)

    林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 本文详细讲解了SpringMVC实例单文件上传.多文件上传.文件列表显示.文件下载. 本文工程 ...

  7. [转]SpringMVC单文件上传、多文件上传、文件列表显示、文件下载

    一.新建一个Web工程,导入相关的包 springmvc的包+commons-fileupload.jar+connom-io.jar+commons-logging,jar+jstl.jar+sta ...

  8. php 合并图片 (将活动背景图片和动态二维码图片合成一张图片)

    <?php //案例一:将活动背景图片和动态二维码图片合成一张图片 //图片一 $path_1 = './background.png'; //图片二 $path_2 = './FU0851_2 ...

  9. Day3-scrapy爬虫下载图片自定义名称

    学习Scrapy过程中发现用Scrapy下载图片时,总是以他们的URL的SHA1 hash值为文件名,如: 图片URL:http://www.example.com/image.jpg 它的SHA1 ...

随机推荐

  1. Maven install命令理解

    每一个构建都需要唯一的坐标来标识位置,我们根据坐标位置就能够下载构建至本地仓库.那么如果我们是内部项目,自定义的构建并不公开至网络上,项目成员又想依赖他怎么办呢?想想maven找寻构建的步骤. 先找寻 ...

  2. 剑指Offer_编程题之重建二叉树

    题目描述 输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树.假设输入的前序遍历和中序遍历的结果中都不含重复的数字.例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7, ...

  3. Lsyncd - 实时文件同步工具(精译)

    原文: http://axkibe.github.io/lsyncd/ 描述 Lsyncd监视本地目录树事件监视器接口(inotify或fsevents).它聚集并组合事件几秒钟,然后生成一个(或多个 ...

  4. Get Docker CE for CentOS

    To get started with Docker CE on CentOS, make sure you meet the prerequisites, then install Docker. ...

  5. 唱吧DevOps的落地,微服务CI/CD的范本技术解读

    1.业务架构:从单体式到微服务 K歌亭是唱吧的一条新业务线,旨在提供线下便捷的快餐式K歌方式,用户可以在一个电话亭大小的空间里完成K歌体验.K歌亭在客户端有VOD.微信和Web共三个交互入口,业务复杂 ...

  6. 查找linux设备的uuid

    [root@ ~]# blkid /dev/vdc /dev/vdc: UUID="bxxxx-xxx-41b9-8146-7da8bd645b92" TYPE="ext ...

  7. ECMAScript简介以及es6新增语法

    ECMAScript简介 ECMAScript与JavaScript的关系 ECMAScript是JavaScript语言的国际化标准,JavaScript是ECMAScript的实现.(前者是后者的 ...

  8. SourceTree安装跳过注册

    问题描述 SourceTree是一个可视化管理版本文件的工具,但是注册的时候需要打开外国的网站. 因此要安装时需要绕过这一步才能使用. 解决方案 1. 在下面路径下创建一个accounts.json文 ...

  9. 终于将 SQL Server 成功迁移至 MySQL8.0 啦!!!

    之前一直使用 SQL Server 作为主数据库而不是 MySQL ,原因之一是单机 SQL Server 性能比 MySQL 强很多,另一个原因是之前客户的系统管理员大多只有 SQL Server ...

  10. 51nod 1135 原根 就是原根...

    %%% dalao Orz ,筛素数到sqrt(n),分解ϕ(p),依次枚举判断就好了 #include<cstdio> #include<cstring> #include& ...