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: