電商商品圖、用戶頭像、文章縮略圖等業務中,數據庫存儲圖片路徑,但文件可能被刪除、遷移失敗、路徑錯誤。頁面直接綁定 ImageUrl 會導致瀏覽器顯示破碎圖標,體驗極差。需要服務端/客戶端自動降級到默認佔位圖。
<appSettings>
<add key="DefaultImageUrl" value="~/Content/Images/placeholder.png" />
</appSettings>
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack) BindData();
}
private void BindData()
{
// 假設從數據庫讀取
var dt = GetDataTable(); // 包含 ImagePath 列
rptList.DataSource = dt;
rptList.DataBind();
}
protected void rptList_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
var img = e.Item.FindControl("imgProduct") as Image;
var row = e.Item.DataItem as DataRowView;
string dbPath = row["ImagePath"]?.ToString();
if (string.IsNullOrWhiteSpace(dbPath) || !FileExists(dbPath))
{
img.ImageUrl = ResolveUrl(ConfigurationManager.AppSettings["DefaultImageUrl"]);
}
else
{
img.ImageUrl = ResolveUrl(dbPath);
}
}
}
private bool FileExists(string virtualPath)
{
// 支持相對路徑 ~/ 或 絕對路徑
string physical = Server.MapPath(virtualPath);
return System.IO.File.Exists(physical);
}
<img src="/images/product/123.jpg"
onerror="this.onerror=null; this.src='/Content/Images/placeholder.png';"
alt="商品圖片" />
關鍵點:this.onerror=null 防止默認圖也失效時陷入無限循環。
<script>
document.addEventListener('DOMContentLoaded', function() {
var defaultImg = '/Content/Images/placeholder.png';
document.querySelectorAll('img[data-fallback]').forEach(function(img) {
img.onerror = function() {
if (this.src !== defaultImg) {
this.onerror = null;
this.src = defaultImg;
}
};
});
});
</script>
<!-- 頁面中 -->
<img data-fallback="true" src="/images/abc.jpg" />
[ToolboxData("<{0}:SmartImage runat=server />")]
public class SmartImage : Image
{
public string DefaultImageUrl { get; set; } = "~/Content/Images/placeholder.png";
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
if (!string.IsNullOrEmpty(ImageUrl))
{
string physical = Context.Server.MapPath(ImageUrl);
if (!System.IO.File.Exists(physical))
{
ImageUrl = ResolveClientUrl(DefaultImageUrl);
}
}
else
{
ImageUrl = ResolveClientUrl(DefaultImageUrl);
}
// 同時輸出 onerror 兜底(雙重保險)
Attributes["onerror"] = $"this.onerror=null; this.src='{ResolveClientUrl(DefaultImageUrl)}';";
}
}
<%@ Register TagPrefix="cc" Namespace="YourControls" Assembly="YourAssembly" %>
<cc:SmartImage ID="imgAvatar" runat="server" ImageUrl='<%# Eval("AvatarPath") %>'
DefaultImageUrl="~/Content/Images/avatar-default.png" />
| 維度 | 服務端判斷 | 客戶端 onerror | 自定義控件 |
|---|---|---|---|
| 可靠性 | ✅ 最高(文件系統級檢查) | ⚠️ 僅網絡層面,無法檢測 403/重定向 | ✅ 雙重保障 |
| 性能影響 | 每請求一次 File.Exists IO | 零服務端開銷 | 同方案一 + 少量屬性輸出 |
| 適用場景 | 動態數據綁定、嚴格業務 | 靜態頁、前端框架渲染、CDN 圖片 | 項目通用組件庫、長期維護 |
| 緩存友好 | 可配合 HttpRuntime.Cache 緩存文件存在性 | 瀏覽器緩存默認圖 | 同方案一 |
private static readonly MemoryCache _fileCache = MemoryCache.Default;
private bool FileExistsCached(string virtualPath)
{
string key = "FileExist:" + virtualPath;
if (_fileCache.Contains(key)) return (bool)_fileCache[key];
bool exists = System.IO.File.Exists(Server.MapPath(virtualPath));
_fileCache.Add(key, exists, DateTimeOffset.Now.AddMinutes(10)); // 緩存 10 分鐘
return exists;
}
via.placeholder.com、placehold.co 或自建 /placeholder/{w}x{h} 動態生成 SVG 佔位圖,減少靜態資源維護。根據項目架構選擇:舊 Web Forms 首選「方案一 + 緩存」;組件化團隊投入「方案三」;前後端分離項目徹底前端化處理。