1. 程式人生 > >MVC5:使用Ajax和HTML5實現檔案上傳功能

MVC5:使用Ajax和HTML5實現檔案上傳功能

引言

在實際程式設計中,經常遇到實現檔案上傳並顯示上傳進度的功能,基於此目的,本文就為大家介紹不使用flash 或任何上傳檔案的外掛來實現帶有進度顯示的檔案上傳功能。

基本功能:實現帶有進度條的檔案上傳功能

高階功能:通過拖拽檔案的操作實現多個檔案上傳功能

背景

HTML5提供了一種標準的訪問本地檔案的方法——File API規格說明,通過呼叫File API 能夠訪問檔案資訊,也可以利用客戶端來驗證上傳檔案的型別和大小是否規範。

該規格說明包含以下幾個介面來使用檔案:

File介面:具有檔案的“讀許可權”,可以獲取檔名,型別,大小等。

FileList介面:指單獨選定的檔案列表,可以通過<input type="file">或拖拽呈現在使用者介面供使用者選擇。

XMLHTTPRequest2是HTML5的無名英雄,XHR2與XMLHttpRequest大體相同,但同時也添加了很多新功能,如下:

1. 增加了上傳/下載二進位制資料

2. 增加了上傳過程中Progess (進度條)事件,該事件包含多部分的資訊:

  • Total:整型值,用於指定傳輸資料的總位元組數。
  • Loaded:整型值,用於指定上傳的位元組。
  • lengthComputable:Bool值用於檢測上傳檔案大小是否可計算。

3. 跨資源共享請求

這些新特性都使得Ajax和HTML5很好的協作,讓檔案上傳變得非常簡單,不再需要使用Flash Player、外部外掛或html的<form>標籤就可以完成,根據伺服器端就可以顯示上傳進度條。

本文會編寫一個小型應用程式,能夠實現以下功能:

  • 上傳單個檔案,提供上傳進度資訊顯示。
  • 將圖片傳送到伺服器時,建立影象縮圖。
  • 通過檔案列表或拖拽操作實現多個檔案上傳。

首先我們需要檢驗瀏覽器是否支援XHR2,File API,FormData及拖拽操作。

編寫程式碼

如何上傳單個檔案並顯示上傳進度?

首先需要做的是建立簡單的View :

  • 定義一個表單,由輸入檔案元素和提交按鈕組成。
  • 使用Bootstrap 進度條顯示進度。
   1:  <div id="FormContent">
   2:             <form id="FormUpload"
   3:             enctype="multipart/form-data"
method="post">
   4:                 <span class="btn btn-success fileinput-button">
   5:                     <i class="glyphicon glyphicon-plus"></i>
   6:                     <span>Add files...</span>
   7:                     <input type="file"
   8:                     name="UploadedFile" id="UploadedFile" />
   9:                 </span>
  10:                 <button class="btn btn-primary start"
  11:                 type="button" id="Submit_btn">
  12:                     <i class="glyphicon glyphicon-upload"></i>
  13:                     <span>Start upload</span>
  14:                 </button>
  15:                  <button class="btn btn-warning cancel"
  16:                  type="button" id="Cancel_btn">
  17:                     <i class="glyphicon glyphicon-ban-circle"></i>
  18:                     <span>close</span>
  19:                 </button>
  20:             </form>
  21:             <div class="progress CustomProgress">
  22:                 <div id="FileProgress"
  23:                 class="progress-bar" role="progressbar"
  24:         aria-valuenow="0" aria-valuemin="0"
  25:         aria-valuemax="100" style="width: 0%;">
  26:                     <span></span>
  27:                 </div>
  28:             </div>
  29:             <div class="InfoContainer">
  30:                 <div id="Imagecontainer"></div>
  31:                 <div id="FileName" class="info">
  32:                 </div>
  33:                 <div id="FileType" class="info">
  34:                 </div>
  35:                 <div id="FileSize" class="info">
  36:                 </div>
  37:             </div>
  38:         </div>

在Onchange 事件中新增輸入檔案元素,並在JS方法SingleFileSelected使用,因此在使用者選擇和修改檔案時都會呼叫此方法。在該方法中,我們將選擇輸入檔案元素和訪問FileList的檔案物件,選擇第一個檔案files[0],因此我們可以得到檔名,檔案型別等資訊。

   1:  function singleFileSelected(evt) {
   2:      //var selectedFile = evt.target.files can use this  or select input file element 
   3:      //and access it's files object
   4:      var selectedFile = ($("#UploadedFile"))[0].files[0];//FileControl.files[0];
   5:      if (selectedFile) {
   6:          var FileSize = 0;
   7:          var imageType = /image.*/;
   8:          if (selectedFile.size > 1048576) {
   9:              FileSize = Math.round(selectedFile.size * 100 / 1048576) / 100 + " MB";
  10:          }
  11:          else if (selectedFile.size > 1024) {
  12:              FileSize = Math.round(selectedFile.size * 100 / 1024) / 100 + " KB";
  13:          }
  14:          else {
  15:              FileSize = selectedFile.size + " Bytes";
  16:          }
  17:          // here we will add the code of thumbnail preview of upload images
  18:         
  19:          $("#FileName").text("Name : " + selectedFile.name);
  20:          $("#FileType").text("type : " + selectedFile.type);
  21:          $("#FileSize").text("Size : " + FileSize);
  22:      }
  23:  }

可以通過File reader物件從記憶體讀取上傳檔案內容。reader 物件提供很多事件,onload,onError以及四種讀取資料的函式readAsBinaryString()readAsText(),readAsArrayBuffer(), readAsDataURL(),result屬性表示檔案內容。該屬性只有當讀操作執行完成後才有效,資料格式根據呼叫的初始化讀操作制定的。

在這裡就不詳細解釋File reader,我們會在SingleFileSelected 方法中使用,用於預覽影象,檢視程式碼:

   1:  if (selectedFile.type.match(imageType)) {
   2:             var reader = new FileReader();
   3:             reader.onload = function (e) {
   4:   
   5:                 $("#Imagecontainer").empty();
   6:                 var dataURL = reader.result;
   7:                 var img = new Image()
   8:                 img.src = dataURL;
   9:                 img.className = "thumb";
  10:                 $("#Imagecontainer").append(img);
  11:             };
  12:             reader.readAsDataURL(selectedFile);
  13:         }


到現在為止,就可看到下圖:

現在需要將已上傳的檔案傳送到伺服器,因此新增Onclick事件,並在JS uploadFile()方法中呼叫,程式碼如下:

   1:  function UploadFile() {
   2:      //we can create form by passing the form to Constructor of formData object
   3:      //or creating it manually using append function 
   4:      //but please note file name should be same like the action Parameter
   5:      //var dataString = new FormData();
   6:      //dataString.append("UploadedFile", selectedFile);
   7:   
   8:      var form = $('#FormUpload')[0];
   9:      var dataString = new FormData(form);
  10:      $.ajax({
  11:          url: '/Uploader/Upload',  //Server script to process data
  12:          type: 'POST',
  13:          xhr: function () {  // Custom XMLHttpRequest
  14:              var myXhr = $.ajaxSettings.xhr();
  15:              if (myXhr.upload) { // Check if upload property exists
  16:                  //myXhr.upload.onprogress = progressHandlingFunction
  17:                  myXhr.upload.addEventListener('progress', progressHandlingFunction, 
  18:                  false); // For handling the progress of the upload
  19:              }
  20:              return myXhr;
  21:          },
  22:          //Ajax events
  23:          success: successHandler,
  24:          error: errorHandler,
  25:          complete:completeHandler,
  26:          // Form data
  27:          data: dataString,
  28:          //Options to tell jQuery not to process data or worry about content-type.
  29:          cache: false,
  30:          contentType: false,
  31:          processData: false
  32:      });
  33:  }

在該方法中,傳送表單,使用Form 資料物件來序列化檔案值,我們可以手動建立formdata資料的例項化,通過呼叫append()方法將域值掛起,或是通過檢索HTML 表單的FormData物件。 

progressHandlingFunction方法會提供檢驗上傳檔案Size 是否可計算,使用e.loaded和e.total計算出已上傳百分之多少的資料。

   1:  function progressHandlingFunction(e) {
   2:      if (e.lengthComputable) {
   3:          var percentComplete = Math.round(e.loaded * 100 / e.total);
   4:          $("#FileProgress").css("width", 
   5:          percentComplete + '%').attr('aria-valuenow', percentComplete);
   6:          $('#FileProgress span').text(percentComplete + "%");
   7:      }
   8:      else {
   9:          $('#FileProgress span').text('unable to compute');
  10:      }
  11:  }

現在已經實現了基本的傳送資料及提供進度條的功能,接下來需要實現伺服器端的程式碼處理,使用upload action方法和uplpader controller 。

在upload 方法中,可以從HttpPostedfileBase物件中獲取檔案資訊,該物件包含上傳的檔案的基本資訊如Filename屬性,Contenttype屬性,inputStream屬性等內容,這些資訊都可以用來驗證伺服器端接收的檔案是否有錯,也可以用來儲存檔案。

   1:  [HttpPost]
   2:   
   3:          public JsonResult Upload(HttpPostedFileBase uploadedFile)
   4:          {
   5:              if (uploadedFile != null && uploadedFile.ContentLength > 0)
   6:              {
   7:                  byte[] FileByteArray = new byte[uploadedFile.ContentLength];
   8:                  uploadedFile.InputStream.Read(FileByteArray, 0, uploadedFile.ContentLength);
   9:                  Attachment newAttchment = new Attachment();
  10:                  newAttchment.FileName = uploadedFile.FileName;
  11:                  newAttchment.FileType = uploadedFile.ContentType;
  12:                  newAttchment.FileContent = FileByteArray;
  13:                  OperationResult operationResult = attachmentManager.SaveAttachment(newAttchment);
  14:                  if (operationResult.Success)
  15:                  {
  16:                      string HTMLString = CaptureHelper.RenderViewToString
  17:                      ("_AttachmentItem", newAttchment, this.ControllerContext);
  18:                      return Json(new
  19:                      {
  20:                          statusCode = 200,
  21:                          status = operationResult.Message,
  22:                          NewRow = HTMLString
  23:                      }, JsonRequestBehavior.AllowGet);
  24:   
  25:                  }
  26:                  else
  27:                  {
  28:                      return Json(new
  29:                      {
  30:                          statusCode = 400,
  31:                          status = operationResult.Message,
  32:                          file = uploadedFile.FileName
  33:                      }, JsonRequestBehavior.AllowGet);
  34:   
  35:                  }
  36:              }
  37:              return Json(new
  38:              {
  39:                  statusCode = 400,
  40:                  status = "Bad Request! Upload Failed",
  41:                  file = string.Empty
  42:              }, JsonRequestBehavior.AllowGet);
  43:          }

能否通過拖拽操作實現多個檔案上傳的功能?

在這一部分,實現相同的uploader,併為uploader新增一些新功能:

  • 允許選擇多個檔案
  • 拖拽操作

現在給Uplodaer View新增新功能:

  • 為輸入檔案元素新增多個屬性,實現同時選擇多個檔案。
  • 新增實現拖拽功能的檔案,如以下程式碼所示:
   1:  <div id="drop_zone">Drop images Here</div>

在JS方法MultiplefileSelected中新增onChange事件,與之前SingleFileSelected的寫法類似,不同的是需要將所有的檔案列出,並允許拖拽檔案。程式碼如下:

   1:  function MultiplefileSelected(evt) {
   2:      evt.stopPropagation();
   3:      evt.preventDefault();
   4:      $('#drop_zone').removeClass('hover');
   5:      selectedFiles = evt.target.files || evt.dataTransfer.files;
   6:      if (selectedFiles) {
   7:          $('#Files').empty();
   8:          for (var i = 0; i < selectedFiles.length; i++) {
   9:              DataURLFileReader.read(selectedFiles[i], function (err, fileInfo) {
  10:                  if (err != null) {
  11:                      var RowInfo = '<div id="File_' + i + '" 
  12:                      class="info"><div class="InfoContainer">' +
  13:                                     '<div class="Error">' + err + '</div>' +
  14:                                    '<div data-name="FileName" 
  15:                                    class="info">' + fileInfo.name + '</div>' +
  16:                                    '<div data-type="FileType" 
  17:                                    class="info">' + fileInfo.type + '</div>' +
  18:                                    '<div data-size="FileSize" 
  19:                                    class="info">' + fileInfo.size() + 
  20:                                    '</div></div><hr/></div>';
  21:                      $('#Files').append(RowInfo);
  22:                  }
  23:                  else {
  24:                      var image = '<img src="' + fileInfo.fileContent + 
  25:                      '" class="thumb" title="' + 
  26:                      fileInfo.name + '" />';
  27:                      var RowInfo = '<div id="File_' + i + '" 
  28:                      class="info"><div class="InfoContainer">' +
  29:                                    '<div data_img="Imagecontainer">' + 
  30:                                    image + '</div>' +
  31:                                    '<div data-name="FileName" 
  32:                                    class="info">' + fileInfo.name + '</div>' +
  33:                                    '<div data-type="FileType" 
  34:                                    class="info">' + fileInfo.type + '</div>' +
  35:                                    '<div data-size="FileSize" 
  36:                                    class="info">' + fileInfo.size() + 
  37: