小弟最近接了個大學生的畢業設計,收了100塊錢讓做一個ASP.NET MVC導入Excel到資料庫的功能,由於以前沒做過就到處搜索資料,但是發現網上的資料不是直接貼一大堆亂起八遭的源碼,就是需要借用NPOI第三方類庫太麻煩了,況且預算才100RMB簡直不值得,所以小弟嘗試自己動手豐衣足食,所以就有了 ...
先上效果圖
1.引言
小弟最近接了個大學生的畢業設計,收了100塊錢讓做一個ASP.NET MVC導入Excel到資料庫的功能,由於以前沒做過就到處搜索資料,但是發現網上的資料不是直接貼一大堆亂起八遭的源碼,就是需要借用NPOI第三方類庫太麻煩了,況且預算才100RMB簡直不值得,所以小弟嘗試自己動手豐衣足食,所以就有了這篇博客。
先上一張小弟的思路圖:
(說明:沒有安裝做流程圖的軟體!湊合著看吧)
2 進入正題
首先跟著小弟先創建一個預設的MVC項目(相信大家都會創建,這裡就不演示了)
第一步
創建一個實體類,這個類是要導入資料庫的對象。為了簡單起見 小弟創建了個學生對象(記錄了他的名字、年齡、各科成績分數)。
先聲明這隻是教學代碼,完全是為了演示用的,你真正做產品的代碼可沒這麼簡單的定義實體。
1 public class Student 2 { 3 4 public Student() 5 { 6 Id = Guid.NewGuid(); 7 } 8 9 //姓名 10 public string Name { get; set; } 11 12 //年齡 13 public int Age { get; set; } 14 15 //語文成績 16 public int ChineseScore { get; set; }
17 //英文成績 19 public int EnglishScore { get; set; } 20 21 //數學成績 22 public int MathScore { get; set; } 23 24 25 }
第二步
實體創建完了,接下來我們創建一個Empty的控制器(名字取自己喜歡的),(這個也太簡單了,我這裡就不演示了)直接上結果:
小弟在這裡創建了個名為UploadExcelController的控制器
1 public class UploadExcelController : Controller 2 { 3 // GET: /UploadExcel 4 public ActionResult Index() 5 { 6 return View(); 7 } 8 }
第三步
控制器創建完了接下來做什麼呢?估計你也猜到了,所有的不是從界面開始嗎!用戶訪問/UploadExcel/Index 的時候總要出來個交互的東西吧。那就先創建一個交互的視圖,游標移到return View();右鍵創建添加視圖
創建視圖有很多種方法,我這裡只是選擇了種簡單的具體看個人習慣創建。
視圖創建完畢後,我們先構造一個瀏覽文件的html標記
1 @{ 2 ViewBag.Title = "View"; 3 } 4 5 <h1>Select the Excel file</h1> 6 7 8 <div class="input-group "> 9 @*文件路徑的文本框*@ 10 <input id="txt_Path" type="text" class="form-control"> 11 12 @*瀏覽本地文件按鈕*@ 13 <span id="btn_Browse" style="cursor:pointer;" onclick="$('input[id=fileUpload]').click();" class="input-group-addon"> 14 <i class="glyphicon glyphicon-folder-open"></i> 瀏覽文件 15 </span> 16 </div>
小弟這裡使用的是MVC自帶的bootstrap框架,相信大家也都能看懂,如果看不懂那些標記的話!建議你先花1天時間入門bootstap框架。
這個時候的效果圖應該是這樣的
在上傳文件到伺服器有個常用的html標記是 <input id="fileUpload" type="file" >,沒錯小弟也用的這個標記,只是它太醜了我把它給隱藏了!用我上面的html標記給替換了。接下來我們開始創建它。
1 <h1>Select the Excel file</h1> 2 3 4 <div class="input-group "> 5 @*文件路徑的文本框*@ 6 <input id="txt_Path" type="text" class="form-control"> 7 8 @*瀏覽本地文件按鈕*@ 9 <span id="btn_Browse" style="cursor:pointer;" onclick="$('input[id=fileUpload]').click();" class="input-group-addon"> 10 <i class="glyphicon glyphicon-folder-open"></i> 瀏覽文件 11 </span> 12 </div> 13 14 <br /><br /> 15 16 17 @using (Html.BeginForm("Browse", "UploadExcel", FormMethod.Post, new { enctype = "multipart/form-data", id = "form_Upload" })) 18 { 19 @Html.AntiForgeryToken() //防止跨站請求偽造(CSRF:Cross-site request forgery)攻擊 20 <input id="fileUpload" type="file" name="file" style="display:none"> //把fileUpload隱藏,原因它太難看了 21 }
紅色的代碼是新增的,Html.BeginForm的擴展方法如果大家不熟悉的話,你就把它看成form表單,因為它最後會生成form表單
<form id="form_Upload" action="/UploadExcel/Browse" method="post" enctype="multipart/form-data">
.......
</form>
哦!這個是基礎知識,相信大家也都知道,(題外話:在能使用@HTML擴展方法的時候,我建議大家不要使用原生的HTML標記,為什麼呢?因為如果你瞭解@HTML擴展方法的運行機制的話你就知道我的用意了, 就比如Html.BeginForm它是根據路由去生成的URL,智能的!安全的!如果自己寫原生HTML標記的話,難免會產生不安全的URL!)
這個form表單就是我們把Excel文件上傳到伺服器用的,
看見form表單的 action="/UploadExcel/Browse",所以我們還要在UploadExcelController控制器里創建第二個名稱叫Browse的Action操作
1 public class UploadExcelController : Controller 2 { 3 // GET: /UploadExcel 4 public ActionResult Index() 5 { 6 return View(); 7 } 8 9 10 11 [HttpPost] 12 [ValidateAntiForgeryToken] 13 [HandleError(View = "~/Views/Shared/Error.cshtml")] 14 public ActionResult Browse(HttpPostedFileBase file) 15 { 16 17 } 18 }
紅色部分是我新增加的Action操作,前面說了竟然創建的form表單是上傳文件到伺服器的,那麼這個操作就是處理上傳文件用的Action,因為它會改變伺服器狀態,所以我定義成Post方法。其餘2個特性是配合@Html擴展方法用的,一個是防止跨站請求偽造(CSRF:Cross-site request forgery)攻擊,一個是統一處理異常頁。你也可以不加,跟我們今天的例子沒關係。我們先不寫這個Action操作的代碼,讓我們再次回到我們創建的視圖頁 。
竟然我們要使用 <input id="fileUpload" type="file" name="file">的功能,但是又嫌棄它太難看了!怎麼辦呢? So easy 只需把我們構造的html標記綁定它就行了。在視圖頁添加如下JS代碼
@section scripts{
<script type="text/javascript"> $('input[id=fileUpload]').change(function () { $('#txt_Path').val($(this).val()); $('#form_Upload').submit(); });
</script>}
這段JS代碼功能就是在<input id="fileUpload" type="file" name="file" style="display:none">選擇文件後就把文件路徑賦給我們自己構造的文本框里,然後就是提交表單,上傳它的選擇的文件到我們的伺服器。
請註意灰色那段代碼是如何用我們自己構建的按鈕綁定到fileUpload的功能上
1 @*瀏覽本地文件按鈕*@ 2 <span id="btn_Browse" style="cursor:pointer;" onclick="$('input[id=fileUpload]').click();" class="input-group-addon"> 3 <i class="glyphicon glyphicon-folder-open"></i> 瀏覽文件 4 </span>
現在就來測試下我們寫的代碼能不能跑!F5運行!如果是以下效果就是正確的
為了瀏覽文件有響應的效果,我們在視圖頁加一小段CSS代碼
1 @section scripts{ 2 3 4 <style type="text/css"> 5 #btn_Browse:hover { 6 color: #3C763D; 7 } 8 </style> 9 10 <script type="text/javascript"> 11 $('input[id=fileUpload]').change(function () { 12 $('#txt_Path').val($(this).val()); 13 $('#form_Upload').submit(); 14 }); 15 16 17 18 </script>}
第五步
到了這裡我們的文件已經可以上傳到我們服務了,如果不信你在Browse操作打個斷點,看看file參數是不是已經接受了文件,如果接受到了說明已經成功一半了!我們還是先不寫Browse操作處理Excel文件的代碼,焦點還是在視圖頁上,在本博客第一張效果圖裡,大家看到瀏覽文件下麵有張table表格嗎?小弟創建這個表格只是為了更好的交互效果,讓使用的人更直觀而已。而且也很簡單!
接下來我們來構建它,在視圖頁新增table表格的代碼
@model Student @using School.Entity <table class="table table-striped table-hover table-bordered"> <tr> <th>@Html.DisplayNameFor(model => model.Name)</th> <th>@Html.DisplayNameFor(model => model.Age)</th> <th>@Html.DisplayNameFor(model => model.ChineseScore)</th> <th>@Html.DisplayNameFor(model => model.EnglishScore)</th> <th>@Html.DisplayNameFor(model => model.MathScore)</th> </tr> @if (ViewBag.Data != null) {
//生成前10條數據 填充表格table foreach (var item in (ViewBag.Data as IEnumerable<Student>).Take(10)) { <tr> <td>@Html.DisplayFor(model => item.Name)</td> <td>@Html.DisplayFor(model => item.Age)</td> <td>@Html.DisplayFor(model => item.ChineseScore)</td> <td>@Html.DisplayFor(model => item.EnglishScore)</td> <td>@Html.DisplayFor(model => item.MathScore)</td> </tr> } } </table> <h5 class="text-info"> 預設顯示前10條記錄 </h5>
在這裡我依然用的@HTML輔助方法,如果你還不會使用它,趕緊花一天學習它,入門非常簡單!非常強大! 設想如果沒有@Html擴展方法 小弟得寫多少硬編碼啊!
然後再次按F5運行先看看效果
這裡的表頭是英文的,如果你想變成中文的話,可以在實體上加上數據註解特性(如下)
1 public class Student 2 { 3 4 [Display(Name="中文成績")] 5 public int ChineseScore { get; set; } 6 7 }
對了還忘了一個東西,就是上傳提交按鈕,我們現在來構建它!在視圖頁form表單下麵添加如下代碼
1 @using (Html.BeginForm("Browse", "UploadExcel", FormMethod.Post, new { enctype = "multipart/form-data", id = "form_Upload" })) 2 { 3 @Html.AntiForgeryToken() 4 <input id="fileUpload" type="file" name="file" style="display:none"> 5 } 6
//紅色部分是我構建的上傳提交按鈕 7 <div class="input-group pull-right" style="margin:0 0 5px 0"> 8 @Html.RouteLink("開始提交", new { action = "Upload" }, new { id="submit", @class = "btn btn-primary ladda-button ", data_style = "expand-right" }) 9 </div>
@Html.RouteLink擴展方法會根據我定義的路由生成一個<a>錨標簽,最後生成如下html標記
<a id="submit" class="btn btn-primary ladda-button" data-style="expand-right" href="/UploadExcel/Upload" >開始提交</a>
在這裡我把它偽裝成了一個button按鈕
data-style="expand-right"這些屬性是我用bootstrap加了個5毛錢的特效,你也可以不用管,也可以使用自己的特效。這個上傳提交按鈕的功能就是最後一個功能,把經過Browse操作轉換成List<T>的數 據導入到我們的資料庫。到現在為止我們的導入Excel的頁面已經全部完成了,當然我的審美觀和前端技術就是渣渣,所以請原諒小弟! href="/UploadExcel/Upload" 這個<a>錨標簽會訪問UploadExcelController控制器的Upload操作,所以我再添加最後一個操作。在控制器添加如下代碼
public class UploadExcelController : Controller { // GET: /UploadExcel public ActionResult Index() { return View(); } [HttpPost] [ValidateAntiForgeryToken] [HandleError(View = "~/Views/Shared/Error.cshtml")] public ActionResult Browse(HttpPostedFileBase file) { return null; } //紅色部分是我新增的Action操作,這個操作的作用是把Browse操作轉換好的List<T> 通過業務服務層 導入我們資料庫 [HandleError(View = "~/Views/Shared/Error.cshtml")] public ActionResult Upload() { return View("UploadSuccess"); //導入成功的頁面 這個頁面就留給大家自己設計吧 } }
現在我們來檢查下我們構造好的頁面,F5運行。如果是下麵的樣子我們就全部完成視圖頁面了
現在我們把重點放在Excel文件的邏輯處理上了,我們先從Browse操作下手,因為此操作負責把我們上傳的Excel文件轉換成List Entity對象,只要轉換成這個集合對象你後面就可以想怎麼插入就怎麼插入了 !想插入MSSQL MYSQL 等不同數據都可以呵呵!因為我們用的ORM框架!
按照我上傳的那個思維圖,我想我先處理驗證!先判斷文件的格式是不是Excel的格式。(Excel的格式是根據版本來的 2007-2010 是xlsx,2003是xls)這裡我只預設了2007-2010 。
在Browse操作添加如下代碼
1 [HttpPost] 2 [ValidateAntiForgeryToken] 3 [HandleError(View = "~/Views/Shared/Error.cshtml")] 4 public ActionResult Browse(HttpPostedFileBase file) 5 { 6 7 if (string.Empty.Equals(file.FileName) || ".xlsx" != Path.GetExtension(file.FileName)) 8 { 9 throw new ArgumentException("當前文件格式不正確,請確保正確的Excel文件格式!"); 10 } 11 12 var severPath = this.Server.MapPath("/files/"); //獲取當前虛擬文件路徑 13 14 var savePath = Path.Combine(severPath, file.FileName); //拼接保存文件路徑 15 16 try 17 { 18 file.SaveAs(savePath); 19 stus = ExcelHelper.ReadExcelToEntityList<Student>(savePath); 20 ViewBag.Data = stus; 21 return View("Index"); 22 } 23 finally 24 { 25 System.IO.File.Delete(savePath);//每次上傳完畢刪除文件 26 } 27 28 }
我在MVC項目的根目錄創建了個files的文件夾,用來保存上傳的Excel文件。然後讀取文件轉換成List Entity對象,然後把它傳給我們創建的視圖。這樣就可以一選擇Excel文件就把數據顯示在頁面上了,轉換數據的核心是這句代碼
stus = ExcelHelper.ReadExcelToEntityList<Student>(savePath);
ExcelHelper是我自己封裝的一個工具庫,我現在來創建它。在根目錄添加一個文件夾,然後添加一個類
ppublic class ExcelHelper { //Excel數據轉List<T> public static IList<T> ReadExcelToEntityList<T>(string filePath) where T : class, new() { DataTable tbl = ReadExcelToDataTable(filePath);//讀取Excel數據到DataTable IList<T> list = DataTableToList<T>(tbl); return list; } //Excel數據轉DataTable 使用的oledb讀取方式 public static DataTable ReadExcelToDataTable(string filePath) { if (filePath == string.Empty) throw new ArgumentNullException("路徑參數不能為空"); string ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0; Persist Security Info=False;Data Source=" + filePath + "; Extended Properties='Excel 8.0;HDR=Yes;IMEX=1'"; OleDbDataAdapter adapter = new OleDbDataAdapter("select * From[Sheet1$]", ConnectionString); //預設讀取的Sheet1,你也可以把它封裝變數,動態讀取你的Sheet工作表 DataTable table = new DataTable("TempTable"); adapter.Fill(table); return table; } //DataTable轉List<T> public static List<T> DataTableToList<T>(DataTable dt) where T : class, new() { if (dt == null) return null; List<T> list = new List<T>(); //遍歷DataTable中所有的數據行 foreach (DataRow dr in dt.Rows) { T t = new T(); PropertyInfo[] propertys = t.GetType().GetProperties(); foreach (PropertyInfo pro in propertys) { //檢查DataTable是否包含此列(列名==對象的屬性名) if (dt.Columns.Contains(pro.Name)) { object value = dr[pro.Name]; value = Convert.ChangeType(value, pro.PropertyType);//強制轉換類型 //如果非空,則賦給對象的屬性 PropertyInfo if (value != DBNull.Value) { pro.SetValue(t, value, null); } } } //對象添加到泛型集合中 list.Add(t); } return list; } }
代碼很簡單我就不翻譯了,就是讀取Excel數據轉各種C#對象,但是這是教學代碼不是產品代碼,我很粗暴的封裝了。你如果要用到生成環境,得還要加上各種邏輯驗證和測試!
好了到了這步,我們就可以出現下麵的效果了:
寫到這裡,感覺最後一個功能把List<T>集合導入資料庫大家應該都會,我就不想再繼續往下寫了。但是還是要說下註意的地方就是導入數據一定要支持事物回滾功能,就是哪怕前面已經導入了幾十條數據了,如果發生一條臟數據導致插入異常,也必須回滾判定全部導入失敗。避免重覆導入,導致資料庫臟數據。
我貼最後導入資料庫的代碼就是Upload操作,我在DAL層是使用了事物處理的,支持回滾!
[HandleError(View = "~/Views/Shared/Error.cshtml")] public ActionResult Upload() { var result= Ioc.Service.IocBll<IStudentBll>.Provide.Insert(stus); if(string.Empty!=result.Success) ViewBag.Info = result.Info; return View("UploadSuccess"); }
完畢