当前位置: 移动技术网 > IT编程>开发语言>.net > .net mvc + layui做图片上传(二)—— 使用流上传和下载图片

.net mvc + layui做图片上传(二)—— 使用流上传和下载图片

2019年01月29日  | 移动技术网IT编程  | 我要评论

中国98vk俱乐部,松田爱华,发财网

摘要:上篇文章写到一种上传图片的方法,其中提到那种方法的局限性,就是上传的文件只能保存在本项目目录下,在其他目录中访问不到该文件。这与浏览器的安全性机制有关,浏览器不允许用户用任意的路径访问服务器上的资源,因为这可能造成服务器上其他位置的信息被泄露。浏览器只允许用户用相对路径直接访问本项目路径下的资源。那么,如果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 = 1,
        //
        // 摘要:
        //     指定操作系统应创建一个新的文件。 如果该文件已存在,则会覆盖它。 这要求 system.security.permissions.fileiopermissionaccess.write
        //     权限。 filemode.create 等效于请求,如果该文件不存在,则使用 system.io.filemode.createnew; 否则为使用 system.io.filemode.truncate。
        //     如果该文件已存在但为隐藏的文件, system.unauthorizedaccessexception 则会引发异常。
        create = 2,
        //
        // 摘要:
        //     指定操作系统应打开现有文件。 若要打开该文件的能力是依赖于指定的值 system.io.fileaccess 枚举。 一个 system.io.filenotfoundexception
        //     如果文件不存在将引发异常。
        open = 3,
        //
        // 摘要:
        //     指定操作系统应打开一个文件,是否它存在,则否则,应创建一个新的文件。 如果使用打开该文件 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 = 4,
        //
        // 摘要:
        //     指定操作系统应打开现有文件。 当打开文件时,应被截断,以便其大小为零字节。 这要求 system.security.permissions.fileiopermissionaccess.write
        //     权限。 尝试从文件中读取使用打开 filemode.truncate 导致 system.argumentexception 异常。
        truncate = 5,
        //
        // 摘要:
        //     如果它存在,并且查找到该文件的末尾,或者创建一个新文件,请打开该文件。 这要求 system.security.permissions.fileiopermissionaccess.append
        //     权限。 filemode.append 可以仅在结合使用 fileaccess.write。 尝试查找该文件将引发结束之前将其置于 system.io.ioexception
        //     异常,并且任何尝试读取失败,将引发 system.notsupportedexception 异常。
        append = 6
    }

 

 常用的几个项为:filemode.create /createnew/open/openorcreate,

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

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

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

   // 摘要:
    //     对于读、 写或读/写访问的文件中定义的常数。
    [comvisible(true)]
    [flags]
    public enum fileaccess
    {
        //
        // 摘要:
        //     对文件的读取访问权限。 可以从文件读取数据。 将与结合起来 write 为读/写访问。
        read = 1,
        //
        // 摘要:
        //     对文件的写入访问权限。 数据可以写入该文件。 将与结合起来 read 为读/写访问。
        write = 2,
        //
        // 摘要:
        //     读取和写入到文件的访问。 可以写入和从文件中读取数据。
        readwrite = 3
    }

 

 filemode和fileaccess对应起来使用,一般open和read组合,create和write组合。

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

 

2.上传功能的实现:

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

        public string upload()
        {
            ///获取上传的文件
            var file = request.files[0];
            //获取上传文件的文件名
            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(0,seekorigin.begin);
            //从位置 0 开始读取上传的文件的数据,数据读取到第一个参数buffer(缓存区)中
            inputstream.read(buffer,0,readlength);
            //创建输出文件流,指定文件的输出位置,模式为创建该新文件,读写权限为 写
            using (var outputstream = new filestream(filepath,filemode.create,fileaccess.write))
            {
                //设置指针的位置为 最开始 的位置
                outputstream.seek(0,seekorigin.begin);
                //从起始位置 将 第一个参数 buffer(缓存区)里的数据写入到 filepath 指定的文件中
                outputstream.write(buffer,0,buffer.length);
            }
        //向前台返回上传文件的文件名,表示上传成功 return jsonconvert.serializeobject(new { name = filename }); }

 

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

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

 

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

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

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

        /// <summary>
        /// 文件上传
        /// </summary>
        /// <returns></returns>
        public string uploadfile()
        {
            var file = request.files[0];
            int buffersize = 4096; 
            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 = 0;
            ///获取或设置光标在当前流中的位置
            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,0,buffer.length);
                if (nread <0 )
                {
                    ///若读取完毕,则跳出循环
                    break;
                }
                try
                {
                    ///将读取到数组中的数据写入新的文件中,再保存到指定的位置
                    using (var outputstream = new filestream(outputpath, filemode.openorcreate, fileaccess.readwrite))
                    {
                        outputstream.seek(offset, seekorigin.begin);//将流中的光标移到第一次读取的数据之后
                        outputstream.write(buffer, 0, 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 = 0;
                    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,0,convert.toint32(readlength));
                    }
                }
                catch (exception exception)
                {

                    throw exception;
                }
                outputstream.write(buffer,0,buffer.length);
                return file(outputstream.getbuffer(),contenttype,filename);
               
            }
        }

 

下面给出演示图片:

 

 

 下载此图:

 

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

 

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

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

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

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

 

 

 

 

 

祝大家小年快乐!

 

 

 

 

 

 

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

 

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网