首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

ASP.NET 圖片上傳與縮圖生成完整實作:從檔案驗證到等比例裁切

     編輯:關於C語言

前言

在早期 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        // 輸出:錯誤訊息
)

核心處理流程

1. 基本驗證

  • 檢查 FileBox.PostedFile.FileName 是否為空
  • 檔案大小限制:1.5 KB ~ 2 MB(ContentLength)
  • 副檔名白名單:.gif、.jpg、.jpeg、.bmp,並對應 ImageFormat 列舉

2. 檔案命名與目錄建立

// 日期目錄: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。

3. 原圖儲存

using (var xImage = Bitmap.FromStream(FileBox.PostedFile.InputStream))
{
    xImage.Save(fullPath, xObject); // 依副檔名決定格式
}

此處已正確使用 using 確保資源釋放,原始碼則為手動 Dispose()。

4. 等比例裁切縮圖核心演算法

目標:產生固定 ThumbnailWidth × ThumbnailHeight 的縮圖,且不變形。策略為「先裁切再縮放」:

  1. 計算原圖長寬比 PhotoHeight / PhotoWidth 與目標比 ThumbnailHeight / ThumbnailWidth。
  2. 若原圖較高(比值較大),保留完整寬度,計算對應高度,從頂部置中裁切。
  3. 若原圖較寬,保留完整高度,計算對應寬度,從左側置中裁切。
  4. 比值相等則直接使用全圖。
// 裁切區域
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。

5. 錯誤處理與回傳

  • 成功:return true,FilePath/FileName 含有效值,LastError = ""
  • 驗證失敗:return false,LastError 含具體原因
  • 例外:直接 throw new Exception(ex.Message + "\r\n" + ex.StackTrace),建議改為記錄日誌後回傳 false

現代化改良建議(ASP.NET Core / .NET 6+)

  1. 跨平台相容:System.Drawing.Common 僅支援 Windows,Linux 需安裝 libgdiplus 或改用 ImageSharp / Magick.NET。
  2. 非同步串流:使用 IFormFile.OpenReadStream() 搭配 await image.LoadAsync(stream)。
  3. 檔名安全:Path.GetRandomFileName() + 原始副檔名,避免時間衝突與路徑遍歷攻擊。
  4. 目錄遍歷防護:驗證 SavePath 不含 ..,使用 Path.GetFullPath 確保在 WebRoot 內。
  5. 記憶體管理:大圖處理時考慮串流式處理,避免完整載入記憶體。

完整程式碼重構範例

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、電商系統至今仍在使用的經典策略。移植到現代架構時,只需替換影像庫與非同步模型,核心邏輯依然適用。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved