MVC5:使用Ajax和HTML5實現文件上傳功能

引言css

在實際編程中,常常遇到實現文件上傳並顯示上傳進度的功能,基於此目的,本文就爲你們介紹不使用flash 或任何上傳文件的插件來實現帶有進度顯示的文件上傳功能。html

基本功能:實現帶有進度條的文件上傳功能web

高級功能:經過拖拽文件的操做實現多個文件上傳功能ajax

背景

HTML5提供了一種標準的訪問本地文件的方法——File API規格說明,經過調用File API 可以訪問文件信息,也能夠利用客戶端來驗證上傳文件的類型和大小是否規範。編程

該規格說明包含如下幾個接口來使用文件:數組

File接口:具備文件的「讀權限」,能夠獲取文件名,類型,大小等。瀏覽器

FileList接口:指單獨選定的文件列表,能夠經過<input type="file">或拖拽呈如今用戶界面供用戶選擇。服務器

XMLHTTPRequest2是HTML5的無名英雄,XHR2與XMLHttpRequest大致相同,但同時也添加了不少新功能,以下:mvc

1. 增長了上傳/下載二進制數據app

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:                                    '</div></div><hr/></div>';
  38:                      $('#Files').append(RowInfo);
  39:                  }
  40:              });
  41:          }
  42:      }
  43:  }

 

在該方法中,將選擇和拖拽文件操做的變量設置爲全局變量selectedFiles,而後掃描 selectedfiles中的每一個文件,將從 DataURLreader對象中調用Read 方法讀取文件。

DataURLreader對象可調用read方法,並將File對象和回調方法做爲read方法參數,在上述方法中咱們建立了FileReader,並修改了FileReader的Onload和onerror回調函數。調用 readAsDataURL 方法來讀文件。

新建FileInfo對象包括了全部的文件信息及內容。

   1:  var DataURLFileReader = {
   2:      read: function (file, callback) {
   3:          var reader = new FileReader();
   4:          var fileInfo = {
   5:              name: file.name,
   6:              type: file.type,
   7:              fileContent: null,
   8:              size: function () {
   9:                  var FileSize = 0;
  10:                  if (file.size > 1048576) {
  11:                      FileSize = Math.round(file.size * 100 / 1048576) / 100 + " MB";
  12:                  }
  13:                  else if (file.size > 1024) {
  14:                      FileSize = Math.round(file.size * 100 / 1024) / 100 + " KB";
  15:                  }
  16:                  else {
  17:                      FileSize = file.size + " bytes";
  18:                  }
  19:                  return FileSize;
  20:              }
  21:          };
  22:          if (!file.type.match('image.*')) {
  23:              callback("file type not allowed", fileInfo);
  24:              return;
  25:          }
  26:          reader.onload = function () {
  27:              fileInfo.fileContent = reader.result;
  28:              callback(null, fileInfo);
  29:          };
  30:          reader.onerror = function () {
  31:              callback(reader.error, fileInfo);
  32:          };
  33:          reader.readAsDataURL(file);
  34:      }
  35:  };

 

使用拖拽操做選擇

因爲大部分瀏覽器如今已經執行拖拽操做,爲了實現拖拽操做,在drop_zone 元素中添加dragover和drop事件。

   1:  var dropZone = document.getElementById('drop_zone');
   2:      dropZone.addEventListener('dragover', handleDragOver, false);
   3:      dropZone.addEventListener('drop', MultiplefileSelected, false);
   4:      dropZone.addEventListener('dragenter', dragenterHandler, false);
   5:      dropZone.addEventListener('dragleave', dragleaveHandler, false);

當文件拖到目標位置時觸發dragover事件,在如下代碼中,咱們修改了默認瀏覽器及datatransfer的dropEffect 屬性,代碼以下:

   1:  function handleDragOver(evt) {
   2:      evt.preventDefault();
   3:      evt.dataTransfer.effectAllowed = 'copy';
   4:      evt.dataTransfer.dropEffect = 'copy';
   5:  }

 

接着在MultiplefileSelected中添加drop事件來處理文件drop操做。

大部分功能已經完善,如今須要添加「上傳按鈕」,經過Onclick事件來調用UploadMultipleFiles方法。

該方法與上文提到的Uploadfile方法相似,不一樣的是手動驗證formdata對象值。

   1:  function UploadMultipleFiles() {
   2:   
   3:      // here we will create FormData manually to prevent sending mon image files
   4:      var dataString = new FormData();
   5:      //var files = document.getElementById("UploadedFiles").files;
   6:      for (var i = 0; i < selectedFiles.length; i++) {
   7:          if (!selectedFiles[i].type.match('image.*')) {
   8:              continue;
   9:          }
  10:         }
  11:  // AJAX Request code here
  12:  }

接下來添加服務器端處理代碼,與上文添加的代碼相似,須要作的就是接受一系列的文件列表,以下:

   1:  public JsonResult UplodMultiple(HttpPostedFileBase[] uploadedFiles)

確保 HttpPostedFileBase 數組名稱與append 方法中的名稱相同,只有這樣,MVC才能映射到文件數組中。

   1:  public JsonResult UplodMultiple(HttpPostedFileBase[] uploadedFiles)
   2:  dataString.append("uploadedFiles", selectedFiles[i]);

 

上傳大文件

爲了容許上傳大文件,若是使用的是 IIS7及以上版本,須要修改Web.config 文件,添加如下代碼:

   1:  <system.webServer>
   2:         <security>
   3:                  <requestFiltering>
   4:                             <requestLimits maxAllowedContentLength="2147483648" />
   5:                  </requestFiltering>
   6:         </security>
   7:  </system.webServer>
   8:   
   9:  <httpRuntime targetFramework="4.5"  maxRequestLength="2097152"/>

 

到這裏全部的功能就能夠實現了,並且最大可上傳2GB的文件。在MVC開發中,文件的上傳和下載都是最常須要實現的功能。在開發時,還能夠藉助一下工具來更好的實現這樣的功能。ComponentOne Studio ASP.NET MVC 這款輕量級控件,它與Visual Studio無縫集成,徹底與MVC6和ASP.NET 5.0兼容,將大幅提升工做效率.

原文連接:http://www.codeproject.com/Articles/1021004/Upload-File-Using-Ajax-And-HTML-in-MVC

 

相關閱讀:

微軟 Build 2017 開發者大會:Azure 與 AI 的快速發展

是什麼讓C#成爲最值得學習的編程語言

從Visual Studio看微軟20年技術變遷

C#開發人員應該知道的13件事情

Visual Studio 2017正式版發佈全紀錄

相關文章
相關標籤/搜索