在早期 ASP.NET WebForms 時代,HtmlInputFile 控制項是處理檔案上傳的標準方式。本文將帶你完整解析一個實用的靜態方法 UploadImages,涵蓋:檔案大小/副檔名驗證、以時間戳命名避免衝突、原圖儲存、等比例裁切縮圖、以及完整的錯誤處理流程。雖然現代 ASP.NET Core 推薦使用 IFormFile,但核心影像處理邏輯(System.Drawing)仍具參考價值。
public static bool UploadImages(
HtmlInputFile FileBox, // 上傳控制項
string SavePath, // 相對儲存路徑(如 "Uploads/")
int ThumbnailWidth, // 縮圖寬度
int ThumbnailHeight, // 縮圖高度
out string FilePath, // 輸出:相對儲存路徑含檔名
out string FileName, // 輸出:實際檔名
out string LastError // 輸出:錯誤訊息
)
FileBox.PostedFile.FileName 是否為空ContentLength).gif、.jpg、.jpeg、.bmp,並對應 ImageFormat 列舉// 日期目錄:20241225/
string strTemp = DateTime.Now.ToShortDateString().Replace("-", "");
// 檔名:HHmmss + 副檔名
FileName = DateTime.Now.ToLongTimeString().Replace(":", "") + Extension;
FilePath = SavePath + strTemp + "/";
// 實體路徑:Server.MapPath("../") + FilePath
FrIEndlib.Function.CreateDirectory(physicalPath); // 自訂工具建立目錄
注意:使用 DateTime.Now 命名在高併發下可能衝突,建議改用 Guid.NewGuid() 或 Interlocked.Increment。
using (var xImage = Bitmap.FromStream(FileBox.PostedFile.InputStream))
{
xImage.Save(fullPath, xObject); // 依副檔名決定格式
}
此處已正確使用 using 確保資源釋放,原始碼則為手動 Dispose()。
目標:產生固定 ThumbnailWidth × ThumbnailHeight 的縮圖,且不變形。策略為「先裁切再縮放」:
PhotoHeight / PhotoWidth 與目標比 ThumbnailHeight / ThumbnailWidth。// 裁切區域
Rectangle NewPhoto;
// ... 計算邏輯 ...
using (var myBitmap = xBitmap.Clone(NewPhoto, PixelFormat.DontCare))
using (var myThumbnail = myBitmap.GetThumbnailImage(
ThumbnailWidth, ThumbnailHeight, ThumbnailCallback, IntPtr.Zero))
{
myThumbnail.Save(thumbPath, xObject); // 儲存為 "s" + 原檔名
}
GetThumbnailImage 內部使用高品質雙三次插值,適合縮圖;若需更高品質可改用 Graphics.DrawImage 搭配 InterpolationMode.HighQualityBicubic。
return true,FilePath/FileName 含有效值,LastError = ""return false,LastError 含具體原因throw new Exception(ex.Message + "\r\n" + ex.StackTrace),建議改為記錄日誌後回傳 falseSystem.Drawing.Common 僅支援 Windows,Linux 需安裝 libgdiplus 或改用 ImageSharp / Magick.NET。IFormFile.OpenReadStream() 搭配 await image.LoadAsync(stream)。Path.GetRandomFileName() + 原始副檔名,避免時間衝突與路徑遍歷攻擊。SavePath 不含 ..,使用 Path.GetFullPath 確保在 WebRoot 內。public static async Task<(bool Success, string RelativePath, string FileName, string Error)>
UploadImageAsync(IFormFile file, string relativeSaveDir, int thumbW, int thumbH)
{
// 1. 驗證
if (file == null || file.Length == 0) return (false, "", "", "無檔案");
if (file.Length > 2_000_000 || file.Length < 1_500) return (false, "", "", "大小須 1.5KB~2MB");
var ext = Path.GetExtension(file.FileName).ToLowerInvariant();
if (!new[] { ".gif", ".jpg", ".jpeg", ".bmp", ".png" }.Contains(ext))
return (false, "", "", "不支援的格式");
// 2. 路徑
var dateDir = DateTime.UtcNow.ToString("yyyyMMdd");
var safeName = $"{Guid.NewGuid():N}{ext}";
var relDir = Path.Combine(relativeSaveDir, dateDir).Replace('\\', '/');
var absDir = Path.Combine(_env.WebRootPath, relDir);
Directory.CreateDirectory(absDir);
var origPath = Path.Combine(absDir, safeName);
var thumbName = "s" + safeName;
var thumbPath = Path.Combine(absDir, thumbName);
// 3. 使用 ImageSharp 處理(跨平台)
using var stream = file.OpenReadStream();
using var image = await SixLabors.ImageSharp.Image.LoadAsync(stream);
// 儲存原圖
await image.SaveAsync(origPath);
// 等比例裁切縮圖
var ratio = (double)thumbW / thumbH;
var imgRatio = (double)image.Width / image.Height;
int cropW, cropH, cropX, cropY;
if (imgRatio > ratio) { cropH = image.Height; cropW = (int)(cropH * ratio); cropX = (image.Width - cropW) / 2; cropY = 0; }
else { cropW = image.Width; cropH = (int)(cropW / ratio); cropX = 0; cropY = (image.Height - cropH) / 2; }
image.Mutate(x => x.Crop(new Rectangle(cropX, cropY, cropW, cropH))
.Resize(thumbW, thumbH));
await image.SaveAsync(thumbPath);
return (true, $"{relDir}/{safeName}", safeName, "");
}
這個經典範例展示了檔案上傳的完整生命週期:驗證 → 命名 → 儲存 → 影像處理 → 縮圖輸出。核心演算法「先裁切後縮放」確保縮圖不變形、保留主體,是許多 CMS、電商系統至今仍在使用的經典策略。移植到現代架構時,只需替換影像庫與非同步模型,核心邏輯依然適用。