在應用系統開發當中,文件的上傳和下載是非常普遍的需求。在基於.NET的C/S架構的項目開發當中,有多種方案可以實現文件的上傳和下載(httpwebrequest、webclient等),而且多采用異步(xxxxAsync或async/await等)的方式實現。而在基於.NET的B/S架構的項目開發當中,雖然webform提供了上傳控件(HttpPostFile),但用戶體驗並不好(頁面刷新,如果上傳大文件則卡死,即不支持分塊上傳),雖然有基於Flash的上傳文件的解決方案,但Flash已經過時(安全性差)。因此我們一般采用基於h5+js的上傳文件插件的解決方案。本文介紹的是使用WebUploader控件結合ASP.NET MVC實現文件的上傳、下載以及上傳成功后將Excel數據保存到SQL Server數據庫中的功能。
關於WebUploader的介紹,讀者可以去查看官方網頁 http://fex.baidu.com/webuploader/,跟ECharts一樣,這也是百度開發的基於h5+js的開源上傳文件插件。官網上面也有詳細的使用介紹,基本上是介紹了js前端的配置和關鍵代碼,但后端代碼並沒有提供,需要讀者自行實現。
在ASP.NET MVC4中使用WebUploader只需要導入開發包中的js和css文件就可以了。比如:
<link href="@Url.Content("~/Scripts/webuploader/webuploader.css")" rel="stylesheet" type="text/css" /> <link href="@Url.Content("~/Scripts/webuploader/bootstrap.css")" rel="stylesheet" type="text/css" /> <link href="@Url.Content("~/Scripts/webuploader/style.css")" rel="stylesheet" type="text/css" /> <script src="@Url.Content("~/Scripts/webuploader/jquery-1.10.2.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/webuploader/bootstrap.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/webuploader/webuploader.js")" type="text/javascript"></script>
然后是Html部分:
<div id="uploader"> <div id="thelist" class="uploader-list"></div> <div class="btns"> <div id="picker">選擇文件</div> <input id="ctlBtn" type="button" value="開始上傳" class="btn btn-default" /> @Html.ActionLink("下載Excel", "DownLoad", null, new { @class = "btn btn-default" }) </div> </div>
這里面的id和class都是webuploader默認為我們提供的,后面的btn-default則是bootstrap為我們提供的。
關鍵的就是js部分代碼:
<script type="text/javascript">
var applicationPath = window.applicationPath === "" ? "" : window.applicationPath || "../../";
var GUID = WebUploader.Base.guid();
$(function () {
var $ = jQuery;
var $list = $('#thelist');
var uploader = WebUploader.create({
auto: false, // 是否自動上傳
swf: applicationPath + '../Scripts/webuploader/Uploader.swf',
//server: applicationPath + 'DAManage/Upload',
server: '@Url.Action("Upload", "Test")',
pick: '#picker',
accept: {
title: 'Excels',
extensions: 'xls,xlsx',
mimeTypes: 'application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
},
chunked: true, //分片上傳
chunkSize: 2048000, //每一片的大小
formData: {
guid: GUID
},
resize: false //不壓縮image
});
//添加進上傳文件隊列
uploader.on('fileQueued', function (file) {
$list.append('<div id="' + file.id + '" class="item">' +
'<h4 class="info">' + file.name + '</h4>' +
'<p class="state">等待上傳...</p>' +
'</div>');
});
//開始上傳
$("#ctlBtn").click(function () {
uploader.upload();
});
//上傳進度
uploader.on('uploadProgress', function (file, percentage) {
var $li = $('#' + file.id),
$percent = $li.find('.progress .progress-bar');
// 避免重復創建
if (!$percent.length) {
$percent = $('<div class="progress progress-striped active">' +
'<div class="progress-bar" role="progressbar" style="width: 0%">' +
'</div>' +
'</div>').appendTo($li).find('.progress-bar');
}
$li.find('p.state').text('上傳中');
$percent.css('width', percentage * 100 + '%');
});
//上傳成功
uploader.on('uploadSuccess', function (file, response) {
$('#' + file.id).find('p.state').text('已上傳');
$.post('@Url.Action("Merge", "Test")', { guid: GUID, fileName: file.name }, function (data) {
$("#uploader .state").html("上傳成功...");
uploader.removeFile(file);
});
});
// 上傳失敗
uploader.on('uploadError', function (file) {
$('#' + file.id).find('p.state').text('上傳出錯');
});
//上傳完成后
uploader.on('uploadComplete', function (file) {
$('#' + file.id).find('.progress').fadeOut();
});
//上傳完畢
uploader.on("uploadFinished", function () {
uploader.reset();
});
});
</script>
上面我給出了基本的注釋,想要獲取參數的詳細定義和說明,請參考:http://fex.baidu.com/webuploader/doc/index.html。
后端控制器部分代碼,主要是上面js中所指向的兩個Action,分別為Upload和Merge。
Upload部分代碼:
/// <summary> /// 上傳Excel /// </summary> /// <returns>1表示失敗,0表示成功</returns> [HttpPost] public ActionResult Upload() { var fileName = Request["name"]; var fileRelName = fileName.Substring(0, fileName.LastIndexOf('.')); int index = Convert.ToInt32(Request["chunk"]); var dir = Server.MapPath("~/Upload"); dir = Path.Combine(dir, fileRelName); if (!System.IO.Directory.Exists(dir)) System.IO.Directory.CreateDirectory(dir); try { string filePath = Path.Combine(dir, index.ToString()); var data = Request.Files["file"]; data.SaveAs(filePath); } catch (Exception) { return Json(new { error = 1 }); } return Json(new { erron = 0 }); }
Merge部分代碼:
/// <summary> /// 合並Excel成功后,將其數據導入數據庫 /// </summary> /// <returns>1表示失敗,0表示成功</returns> public ActionResult Merge() { var uploadDir = Server.MapPath("~/Upload"); var fileName = Request["fileName"]; var fileRelName = fileName.Substring(0, fileName.LastIndexOf('.')); var dir = Path.Combine(uploadDir, fileRelName);//臨時文件夾 var files = System.IO.Directory.GetFiles(dir); var finalPath = Path.Combine(uploadDir, fileName); var fs = new FileStream(finalPath, FileMode.Create); foreach (var part in files.OrderBy(x => x.Length).ThenBy(x => x))//排一下序,保證從0-N塊寫入 { var bytes = System.IO.File.ReadAllBytes(part); fs.Write(bytes, 0, bytes.Length); System.IO.File.Delete(part);//刪除分塊 } fs.Flush(); fs.Close(); System.IO.Directory.Delete(dir);//刪除文件夾 try { //讀取上傳的Excel並保存到數據庫 DbHelper.Excel2DB(finalPath, "Sheet1"); } catch (Exception) { return Json(new { error = 1 }); } return Json(new { error = 0 }); }
WebUploader是基於分塊上傳(這個設計主要針對大文件的上傳)的,所以后端的處理也是分塊合並的。Merge中的Excel2DB方法是將上傳到服務器的Excel文件中的數據保存到數據庫的方法,其中涉及Excel文件內容的讀取(NPOI,支持xls和xlsx類型)和EF(5.0)實現數據的保存(保存到SQLServer)。具體的代碼可以參考本篇博客最后的示例代碼下載。
而關於文件的下載,其實ASP.NET MVC已經提供了FileResult類型,只需要返回File對象就可以了,具體的Action代碼如下:
/// <summary> /// 下載excel /// </summary> /// <returns></returns> public FileResult DownLoad() { var path = Server.MapPath("~/Upload/"); var file = System.IO.Directory.GetFiles(path).OrderByDescending(t => new FileInfo(t).CreationTime).FirstOrDefault(); return File(file, "application/vnd.ms-excel", new FileInfo(file).Name); }
File對應的第二個參數是Content-Type,由於這里要下載Excel,所以用了application/vnd.ms-excel。關於如何確定各種文件類型的Content-Type可以查看這個網址里的內容:http://tool.oschina.net/commons。
對應的前端代碼上面已經貼過了,代碼如下:
@Html.ActionLink("下載Excel", "DownLoad", null, new { @class = "btn btn-default" })
