ASP.NET MVC 範例:匯入 Excel 取代逐筆輸入
0 |
寫網頁的同學們應該都有遇過這種需求?只要是矩陣式網頁輸入介面(如下圖),幾乎都會遇到使用者許願:「我能不能先在 Excel 敲好再用匯入的?」
說實在話,如果我是使用者也會覺得這是好用介面的必備條件。網頁介面再怎麼厲害,跟 Excel 永遠不會在同一個量級,加上許多使用者終日與 Excel 為伍,要求他們放著熟練順手的工具不用,硬要在網頁重敲一遍,很難不引起抱怨。
如此實用的需求,硬是推辭不做也說不過去,只會顯得我們功力不足。所幸,借助好用的 ClosedXML程式庫,要在 ASP.NET MVC 寫出 Excel 轉成網頁輸入並不難,這篇文章就來簡單示範一下。
假設網頁如上圖有 4 x 4 個輸入欄位要填寫,我先準備一個 Excel 預先填好內容當實驗材料:
前端程式長這樣:
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Excel 匯入示範</title>
<style>
td input {
width: 80px;
}
</style>
</head>
<body>
<div>
<div>請輸入資料</div>
<table>
<tr>
<td><input id="C00" /></td>
<td><input id="C01" /></td>
<td><input id="C02" /></td>
<td><input id="C03" /></td>
</tr>
<tr>
<td><input id="C10" /></td>
<td><input id="C11" /></td>
<td><input id="C12" /></td>
<td><input id="C13" /></td>
</tr>
<tr>
<td><input id="C20" /></td>
<td><input id="C21" /></td>
<td><input id="C22" /></td>
<td><input id="C23" /></td>
</tr>
</table>
<form action="@Url.Content("~/Home/ParseAsCsv?callback=parent.fillCsv")" method="POST"
enctype="multipart/form-data" target="resultFrame">
<button type="submit" id="btnReadExcel">從Excel匯入</button>
<input type="file" name="excelFile" />
<script>
function fillCsv(csv) {
var rows = csv.split('\n');
//TODO: 未考慮筆數不吻合的情況
for (var r = 0; r < rows.length; r++) {
var cells = rows[r].split('\t');
for (var c = 0; c < cells.length; c++) {
var inp = document.getElementById("C" + r + c);
if (inp) inp.value = cells[c];
}
}
}
</script>
</form>
<iframe name="resultFrame" style="display:none;"></iframe>
</div>
</body>
</html>
我在最下方加了一個 <form >,enctype 設 "mutilpar/form-data",搭配 <input type="file" >,走標準的網頁檔案上傳。伺服器端讀取 Excel 檔後用 ClosedXML 解析,將資料轉成 CSV 傳回前端(當然,也可考慮轉成二維字串陣列 JSON,但我個人偏好 CSV,通用性高且較直覺好偵錯),用一小段 JavaScript 解析 CSV 將資料逐欄填入對映欄位。另外,我還用了 form target 指向隱形 iframe 技巧,讓它有 AJAX 更新效果。
後端的程式碼也不複雜,ParseAsCsv Action 接收 IEnumerable<HttpPostedFileBase> 取得 Excel 內容,以 ClosedXML 解析後轉成 CSV (為省略處理內含逗號的情況,我採用 "\t" 分隔,但此處先不考慮欄位內包換行符號的案例),接著將 CSV 字串內容交給前端指定的回呼 JavaScript 函數處理。由於 POST 傳回網頁被包在 iframe,函數名稱前方要加上 parent。
using ClosedXML.Excel;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace Demo.Controllers
{
public class HomeController : Controller
{
// GET: Home
public ActionResult Index()
{
return View();
}
/// <summary>
/// 上傳Excel解析為CSV
/// </summary>
/// <param name="excelFile">上傳檔案</param>
/// <param name="callback">處理結果之 JavaScript 函數名稱</param>
/// <returns></returns>
public ActionResult ParseAsCsv(IEnumerable<HttpPostedFileBase> excelFile, string callback)
{
try
{
if (excelFile == null || excelFile.First() == null) throw new ApplicationException("未選取檔案或檔案上傳失敗");
if (excelFile.Count() != 1) throw new ApplicationException("請上傳單一檔案");
var file = excelFile.First();
if (Path.GetExtension(file.FileName) != ".xlsx") throw new ApplicationException("請使用Excel 2007(.xlsx)格式");
var stream = file.InputStream;
XLWorkbook wb = new XLWorkbook(stream);
if (wb.Worksheets.Count > 1)
throw new ApplicationException("Excel檔包含多個工作表");
var csv =
string.Join("\n",
wb.Worksheets.First().RowsUsed().Select(row =>
string.Join("\t",
row.Cells(1, row.LastCellUsed(false).Address.ColumnNumber)
.Select(cell => cell.GetValue<string>()).ToArray()
)).ToArray());
return Content($@"<script>
{callback}({JsonConvert.SerializeObject(csv)});
</script>", "text/html");
}
catch (Exception ex)
{
return Content($"<script>alert({JsonConvert.SerializeObject(ex.Message)})</script>", "text/html");
}
}
}
}
廢話不多說,請看示範:
An example of using ClosedXML to import Excel as webpage input fields in ASP.NET MVC.
Comments
Be the first to post a comment