最簡單換膚 = 切換 CSS 檔案。本文介紹兩種無需修改每個頁面的全站換膚實作:Page.Base 重寫 Render 與 HttpModule 攔截回應。
public class PageBase : System.Web.UI.Page
{
protected override void Render(HtmlTextWriter writer)
{
using (StringWriter sw = new StringWriter())
using (HtmlTextWriter hw = new HtmlTextWriter(sw))
{
base.Render(hw);
hw.Flush();
string html = sw.ToString();
// 取得當前使用者選擇的 CSS URL (Session/Profile/Cookie/DB)
string cssUrl = GetCurrentThemeCssUrl(); // 如 "/Styles/Blue.css"
string linkTag = $"";
// 插入到 之前
int pos = html.IndexOf("", StringComparison.OrdinalIgnoreCase);
if (pos > 0)
html = html.Insert(pos, linkTag);
writer.Write(html);
}
}
private string GetCurrentThemeCssUrl() { /* 實作略 */ return "/Styles/Default.css"; }
}
所有頁面繼承 PageBase 即可自動套用。
public class ThemeInjectionModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.PostRequestHandlerExecute += OnPostRequestHandlerExecute;
}
private void OnPostRequestHandlerExecute(object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)sender;
HttpResponse response = app.Context.Response;
// 僅處理 HTML 回應
if (response.ContentType != "text/html") return;
// 安裝 Response Filter
response.Filter = new ThemeInjectionFilter(response.Filter, GetCurrentThemeCssUrl());
}
private string GetCurrentThemeCssUrl() { /* 實作略 */ return "/Styles/Default.css"; }
}
public class ThemeInjectionFilter : Stream
{
private readonly Stream _baseStream;
private readonly string _linkTag;
private readonly MemoryStream _buffer = new MemoryStream();
private bool _injected = false;
public ThemeInjectionFilter(Stream baseStream, string cssUrl)
{
_baseStream = baseStream;
_linkTag = $"";
}
public override void Write(byte[] buffer, int offset, int count)
{
if (!_injected)
{
_buffer.Write(buffer, offset, count);
string html = Encoding.UTF8.GetString(_buffer.ToArray());
int pos = html.IndexOf("", StringComparison.OrdinalIgnoreCase);
if (pos >= 0)
{
html = html.Insert(pos, _linkTag);
byte[] newBytes = Encoding.UTF8.GetBytes(html);
_baseStream.Write(newBytes, 0, newBytes.Length);
_injected = true;
_buffer.SetLength(0); // 釋放緩衝
}
}
else
{
_baseStream.Write(buffer, offset, count);
}
}
// 其他 Stream 抽象成員轉發給 _baseStream...
}
web.config 註冊:
<system.webServer>
<modules>
<add name="ThemeInjection" type="MyApp.ThemeInjectionModule" />
</modules>
</system.webServer>
| 維度 | PageBase Render | HttpModule + Filter |
|---|---|---|
| 侵入性 | 需所有頁面繼承基類 | 零修改,配置即生效 |
| 效能 | 完整 HTML 進記憶體字串 | 串流處理,記憶體友善 |
| 靈活性 | 易於在頁面層級覆寫/排除 | 需在 Filter 邏輯判斷排除路徑 |
| 適用範圍 | WebForms 傳統專案 | WebForms / MVC / Core 通用 |
web.config <pages theme="Blue" /> 或 Page.Theme = "Blue":ASP.NET 內建主題機制 (App_Themes 資料夾),自動套用 .skin/ .css。Profile 儲存使用者偏好:Profile.Theme。HttpModule + Response Filter 是企業級全站換膚/注入 SEO/安全標頭/AMP 轉換的標準架構模式。