本控制項解決傳統 <asp:FileUpload> 需整頁 PostBack 的體驗缺口,採用 隱藏 Iframe + 父頁面 JavaScript 回呼 架構,實現「無整頁重新整理」的上傳體驗,並整合縮圖生成、格式驗證、檔名衝突處理、原圖自動清理等企業級功能。
| 檔案 | 職責 |
|---|---|
CtFileUpLoad.ascx | 使用者控制項,含 Iframe、隱藏 TextBox 存檔名、預覽區 DIV |
CtFileUpLoad.ascx.cs | 屬性定義、參數組裝傳遞給 Iframe |
FileUpLoad.aspx | Iframe 內實際上傳頁面,含 FileUpload 控制項 |
FileUpLoad.aspx.cs | 伺服器端處理:儲存、縮圖、水印、回呼父頁面 |
<%@ Control Language="C#" AutoEventWireup="true"
CodeFile="CtFileUpLoad.ascx.cs" Inherits="WebParts_CtFileUpLoad" %>
<table cellpadding="0" cellspacing="0">
<tr>
<td>
<iframe src="/WebParts/FileUpLoad.aspx?<%=ParsValue %>"
width="240px" height="22px" frameborder="0" scrolling="no"></iframe>
</td>
<td>
<asp:TextBox runat="server" ID="tbFileName" style="display:none"></asp:TextBox>
<div id="dvImg" runat="server" style="position:absolute; margin-left:50px;"></div>
</td>
</tr>
</table>
<script>
function <%=ClientID %>CallLoaded() {
<% =OnLoaded %>;
}
</script>
public partial class WebParts_CtFileUpLoad : UserControl
{
public bool AutoFileName { get; set; } = true; // 依時間戳自動命名
public string UpLoadPath { get; set; } = "UPLOADFILES"; // 上傳資料夾
public string OnLoaded { get; set; } = ""; // 上傳完成客戶端回呼
public string SupportExtension { get; set; } = "gif|png|jpeg|jpg";
public bool ShowImg { get; set; } = true; // 是否顯示預覽
public string FileName => tbFileName.Text; // 取得儲存檔名
protected string ParsValue = "";
protected void Page_Load(object sender, EventArgs e)
{
ParsValue = $"AutoFileName={AutoFileName}&SupportExtension={SupportExtension}" +
$"&UpLoadPath={UpLoadPath}&ID={ClientID}";
}
}
<asp:FileUpload ID="FileUpload1" runat="server" onchange="upload(this);" />
<asp:Button ID="btUp" runat="server" style="display:none" OnClick="btUp_Click" />
<script>
function upload(ob) {
var expStr = new RegExp(".*(" + "<%=SupportExtension%>" + ")$", "i");
if (!expStr.test(ob.value)) {
alert("上傳檔案類型錯誤。\n支援格式:" + "<%=SupportExtension%>");
return;
}
document.getElementById("<%=btUp.ClientID %>").click(); // 觸發伺服器端上傳
}
</script>
protected void btUp_Click(object sender, EventArgs e)
{
if (!FileUpload1.HasFile) return;
// 1. 檔案格式驗證
string ext = Path.GetExtension(FileUpload1.FileName).ToLower();
string[] allowed = SupportExtension.Split('|');
if (!allowed.Contains(ext.TrimStart('.'))) { ShowError("格式不支援"); return; }
// 2. 檔案大小限制 (2MB)
if (FileUpload1.PostedFile.ContentLength > 2 * 1024 * 1024) { ShowError("超過 2MB"); return; }
// 3. 決定檔名
string fileName = AutoFileName
? $"{DateTime.Now.Ticks}{ext}"
: FileUpload1.FileName;
string thumbName = "s_" + fileName;
string savePath = Server.MapPath($"~/{UpLoadPath}/");
string fullPath = Path.Combine(savePath, fileName);
string thumbPath = Path.Combine(savePath, thumbName);
// 4. 避免重複
if (File.Exists(fullPath)) { ShowError("檔案已存在"); return; }
try
{
// 5. 儲存原圖
FileUpload1.SaveAs(fullPath);
// 6. 產生縮圖 (根據原圖尺寸動態決定規格)
using (var img = Image.FromFile(fullPath))
{
if (img.Width > 800 || img.Height > 600)
MakeThumbnail(fullPath, thumbPath, 800, 600, "W");
else
MakeThumbnail(fullPath, thumbPath, 300, 230, "W");
}
// 7. 回呼父頁面:填入隱藏欄位、顯示縮圖、觸發 OnLoaded
string script = $@"
parent.document.getElementById('{ClientID}_tbFileName').value = '{thumbName}';
parent.{ClientID}CallLoaded();
var dv = parent.document.getElementById('{ClientID}_dvImg');
dv.innerHTML = '<img src=\"{UpLoadPath}/{thumbName}\" style=\"width:{w}px;height:{h}px\" />';
dv.style.display = 'block';
alert('上傳成功');
";
ClientScript.RegisterStartupScript(GetType(), "success", script, true);
}
catch (Exception ex) { ShowError(ex.Message); }
finally
{
// 8. 關鍵:刪除原圖,只保留縮圖
if (File.Exists(fullPath)) File.Delete(fullPath);
}
}
public void MakeThumbnail(string src, string dest, int width, int height, string mode)
{
using (var srcImg = Image.FromFile(src))
{
int toW = width, toH = height, x = 0, y = 0, ow = srcImg.Width, oh = srcImg.Height;
switch (mode)
{
case "W": toH = oh * width / ow; break; // 固定寬,高按比例
case "H": toW = ow * height / oh; break; // 固定高,寬按比例
case "Cut": // 裁切不變形
if ((double)ow / oh > (double)toW / toH)
{ oh = srcImg.Height; ow = oh * toW / toH; y = 0; x = (srcImg.Width - ow) / 2; }
else
{ ow = srcImg.Width; oh = ow * toH / toW; x = 0; y = (srcImg.Height - oh) / 2; }
break;
}
using (var bmp = new Bitmap(toW, toH))
using (var g = Graphics.FromImage(bmp))
{
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.SmoothingMode = SmoothingMode.HighQuality;
g.Clear(Color.Transparent);
g.DrawImage(srcImg, new Rectangle(0, 0, toW, toH),
new Rectangle(x, y, ow, oh), GraphicsUnit.Pixel);
bmp.Save(dest, ImageFormat.Jpeg);
}
}
}
<%@ Register Src="~/WebParts/CtFileUpLoad.ascx" TagPrefix="uc" TagName="FileUpload" %>
<uc:FileUpload ID="fuProductImage" runat="server"
UpLoadPath="ProductImages"
SupportExtension="jpg|png|gif"
AutoFileName="true"
OnLoaded="onImageUploaded"
ShowImg="true" />
<script>
function onImageUploaded() {
var fileName = document.getElementById('<%=fuProductImage.ClientID%>_tbFileName').value;
console.log('上傳完成:', fileName);
// 可觸發後續邏輯:啟用送出按鈕、更新預覽區等
}
</script>
multiple 屬性 + FormData AJAX 上傳此控制項展示了 ASP.NET WebForms 時代解決「無刷新上傳」的經典模式:Iframe 隔離上傳流程 + 父子頁面 JavaScript 通訊 + 伺服器端完整影像處理管線。雖然現代專案多改用 WebAPI + 前端框架,但此架構在維護舊系統、快速原型開發、內部管理後台等場景依然具備高開發效率與零前端建置依賴的優勢。