本文解析一個精簡且功能完整的 ASP.NET 相簿管理核心類別 PhotoManager,涵蓋相片/相簿的 CRUD、權限控制、縮圖產生、批次上傳列表等實用功能。程式碼採用 ADO.NET 執行預存程序,搭配 System.Drawing 進行影像處理,是中小型相簿系統的優良參考架構。
SqlConnection/SqlCommand 呼叫預存程序,參數化查詢防止 SQL Injection。HttpContext.Current.User.IsInRole("Friends") 或 "Administrators" 決定是否過濾私有內容。ResizeImageFile 依指定長邊等比縮放,支援 Small(100px)、Medium(200px)、Large(600px) 三種尺寸。Photo、Album、PhotoSize 等簡單 POCO 類別(程式碼中未列出,需自行定義)。public static Stream GetPhoto(int photoid, PhotoSize size)
{
using (SqlConnection conn = new SqlConnection(ConnString))
using (SqlCommand cmd = new SqlCommand("GetPhoto", conn))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter("@PhotoID", photoid));
cmd.Parameters.Add(new SqlParameter("@Size", (int)size));
bool filter = !(HttpContext.Current.User.IsInRole("Friends") || HttpContext.Current.User.IsInRole("Administrators"));
cmd.Parameters.Add(new SqlParameter("@IsPublic", filter));
conn.Open();
object result = cmd.ExecuteScalar();
try { return new MemoryStream((byte[])result); }
catch { return null; }
}
}
重點:回傳 MemoryStream 讓呼叫端直接輸出至 Response 或繫結至 Image 控制項;若查無資料或轉型失敗回傳 null。
public static void AddPhoto(int AlbumID, string Caption, byte[] BytesOriginal)
{
using (SqlConnection conn = new SqlConnection(ConnString))
using (SqlCommand cmd = new SqlCommand("AddPhoto", conn))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter("@AlbumID", AlbumID));
cmd.Parameters.Add(new SqlParameter("@Caption", Caption));
cmd.Parameters.Add(new SqlParameter("@BytesOriginal", BytesOriginal));
cmd.Parameters.Add(new SqlParameter("@BytesFull", ResizeImageFile(BytesOriginal, 600)));
cmd.Parameters.Add(new SqlParameter("@BytesPoster", ResizeImageFile(BytesOriginal, 198)));
cmd.Parameters.Add(new SqlParameter("@BytesThumb", ResizeImageFile(BytesOriginal, 100)));
conn.Open();
cmd.ExecuteNonQuery();
}
}
設計亮點:一次上傳原始圖,伺服器端同步產生三種尺寸儲存,前端可依裝置需求請求不同解析度,減少頻寬浪費。
private static byte[] ResizeImageFile(byte[] imageFile, int targetSize)
{
using (Image oldImage = Image.FromStream(new MemoryStream(imageFile)))
{
Size newSize = CalculateDimensions(oldImage.Size, targetSize);
using (Bitmap newImage = new Bitmap(newSize.Width, newSize.Height, PixelFormat.Format24bppRgb))
using (Graphics canvas = Graphics.FromImage(newImage))
{
canvas.SmoothingMode = SmoothingMode.AntiAlias;
canvas.InterpolationMode = InterpolationMode.HighQualityBicubic;
canvas.PixelOffsetMode = PixelOffsetMode.HighQuality;
canvas.DrawImage(oldImage, new Rectangle(new Point(0, 0), newSize));
MemoryStream ms = new MemoryStream();
newImage.Save(ms, ImageFormat.Jpeg);
return ms.GetBuffer();
}
}
}
private static Size CalculateDimensions(Size oldSize, int targetSize)
{
Size newSize = new Size();
if (oldSize.Height > oldSize.Width)
{
newSize.Width = (int)(oldSize.Width * ((float)targetSize / oldSize.Height));
newSize.Height = targetSize;
}
else
{
newSize.Width = targetSize;
newSize.Height = (int)(oldSize.Height * ((float)targetSize / oldSize.Width));
}
return newSize;
}
採用高品質雙三次插值,保持長寬比,輸出 JPEG 格式位元組陣列,適合直接寫入資料庫 BLOB 欄位。
GetAlbums()、AddAlbum()、EditAlbum()、RemoveAlbum():標準 CRUD,同樣套用權限過濾。GetRandomAlbumID():從非空相簿中隨機挑選,供首頁推薦使用。public static ICollection ListUploadDirectory()
{
DirectoryInfo d = new DirectoryInfo(HttpContext.Current.Server.MapPath("~/Upload"));
return d.GetFileSystemInfos("*.jpg");
}
列出伺服器 /Upload 目錄下所有 JPG,供批次上傳介面預覽。
程式碼假設資料庫已建立以下預存程序(需自行實作):
GetPhoto, GetFirstPhoto, GetPhotos, AddPhoto, RemovePhoto, EditPhotoGetAlbums, AddAlbum, RemoveAlbum, EditAlbum, GetNonEmptyAlbums資料表建議欄位:Albums(AlbumID, Caption, IsPublic, NumberOfPhotos)、Photos(PhotoID, AlbumID, Caption, BytesOriginal, BytesFull, BytesPoster, BytesThumb, IsPublic)。
ConnString 移至 web.config <connectionStrings>,避免硬編碼。pooling=true,確保高併發效能。SqlFileStream (Filestream) 或物件儲存,避免記憶體壓力。catch 僅回傳 null/吞噬例外,生產環境應記錄錯誤並拋出自訂例外。PhotoManager 展示了以 ADO.NET + 預存程序 + GDI+ 打造相簿核心邏輯的經典模式。雖然現代開發傾向 ORM (EF Core) 與雲端儲存,但理解這層基礎實作有助於在受限環境(無 NuGet、舊版 .NET)下快速交付,也是學習影像處理與資料存取整合的優良教材。