.net mvc + layui做圖片上傳(二)—— 使用流上傳和下載圖片


摘要:上篇文章寫到一種上傳圖片的方法,其中提到那種方法的局限性,就是上傳的文件只能保存在本項目目錄下,在其他目錄中訪問不到該文件。這與瀏覽器的安全性機制有關,瀏覽器不允許用戶用任意的路徑訪問服務器上的資源,因為這可能造成服務器上其他位置的信息被泄露。瀏覽器只允許用戶用相對路徑直接訪問本項目路徑下的資源。那么,如果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

 

 

 

 

 

祝大家小年快樂!

 

 

 

 

 

 

如有幫助,能不能點個推薦呢,哈哈哈!有點恬不知恥哈,不要介意!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM