有時需在頁面輸出到瀏覽器前,統一修改 HTML(如注入腳本、關鍵字替換、壓縮、加簽章)。ASP.NET 提供多種攔截點:Response.Filter、HttpModule 事件、甚至反射替換內部 _writer 欄位。本文詳細實作三種方案並比較優劣。
HttpResponse.Output 屬性為 TextWriter,內部實際寫入的是私有欄位 _writer。若能替換此欄位為自訂 StringWriter,即可捕獲所有 Response.Write、控制項 Render 輸出的內容,處理完再寫回原輸出流。
public partial class _Default : System.Web.UI.Page
{
StringBuilder content = new StringBuilder();
TextWriter tw_old, tw_new;
FieldInfo tw_field;
protected void Page_Load(object sender, EventArgs e)
{
var context = HttpContext.Current;
tw_old = context.Response.Output; // 原始輸出
tw_new = new StringWriter(content); // 暫存緩衝
var type_rp = context.Response.GetType();
// 取得私有欄位 _writer
tw_field = type_rp.GetField("_writer",
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
tw_field.SetValue(context.Response, tw_new); // 替換
}
protected override void Render(HtmlTextWriter writer)
{
base.Render(writer); // 此時輸出寫入 tw_new (StringWriter)
// 還原原始輸出
tw_field.SetValue(HttpContext.Current.Response, tw_old);
// 自訂處理:注入註解
content.AppendLine("<!-- 江湖小子 -->");
// 寫回真實輸出
HttpContext.Current.Response.Write(content.ToString());
}
}
適用: 單一頁面特殊處理、快速原型。
缺點: 依賴私有欄位名稱(_writer),跨 .NET 版本可能失效;每頁需重複程式碼。
public class OutputCaptureModule : IHttpModule
{
private HttpApplication _app;
private TextWriter _twOld, _twNew;
private StringBuilder _content;
private FieldInfo _writerField;
public void Init(HttpApplication context)
{
_app = context;
_app.PreRequestHandlerExecute += OnPreRequestHandlerExecute;
}
void OnPreRequestHandlerExecute(object sender, EventArgs e)
{
var context = _app.Context;
var page = context.Handler as Page;
if (page == null) return; // 非頁面請求(如 WebService、Handler)略過
page.Unload += OnPageUnload; // 頁面渲染完成後觸發
_content = new StringBuilder();
_twOld = context.Response.Output;
_twNew = new StringWriter(_content);
var type = context.Response.GetType();
_writerField = type.GetField("_writer",
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
_writerField.SetValue(context.Response, _twNew);
}
void OnPageUnload(object sender, EventArgs e)
{
// 還原
_writerField.SetValue(HttpContext.Current.Response, _twOld);
// 處理內容
_content.AppendLine("<!-- 江湖小子 -->");
HttpContext.Current.Response.Write(_content.ToString());
}
public void Dispose() { _app = null; }
}
註冊於 web.config:
<system.webServer>
<modules>
<add name="OutputCapture" type="Namespace.OutputCaptureModule" />
</modules>
</system.webServer>
優點: 全域生效、不修改頁面程式碼、可針對特定路徑/副檔名攔截。
缺點: 同樣依賴反射私有欄位;Unload 事件較晚,若前面已 Response.End() 可能不觸發。
此法改用 Response.Filter 屬性(Stream),避開反射,相容性更好:
public class StreamFilterModule : IHttpModule
{
public void Init(HttpApplication app)
{
app.BeginRequest += (s, e) =>
{
var ctx = ((HttpApplication)s).Context;
ctx.Response.Filter = new MemoryStream(); // 替換為記憶體流
};
app.EndRequest += (s, e) =>
{
var ctx = ((HttpApplication)s).Context;
var filter = ctx.Response.Filter as MemoryStream;
if (filter == null) return;
filter.Position = 0;
var html = new StreamReader(filter, ctx.Response.ContentEncoding).ReadToEnd();
// 處理 HTML
html += "<!-- jhxz -->";
// 寫回原始流(需先清空 Filter)
ctx.Response.Filter = null;
ctx.Response.Write(html);
};
}
public void Dispose() { }
}
注意: Response.Filter 僅攔截 Response.Write/Response.BinaryWrite,控制項 Render 直接寫入 HtmlTextWriter(繞過 Filter)可能漏捕。若需完整捕獲,仍需反射替換 _writer 或使用 HttpResponse.Filter 搭配 Page.Render 重寫。
.NET Core 時代請使用 Middleware:
public class HtmlInjectionMiddleware
{
private readonly RequestDelegate _next;
public HtmlInjectionMiddleware(RequestDelegate next) => _next = next;
public async Task InvokeAsync(HttpContext context)
{
var originalBody = context.Response.Body;
using (var memStream = new MemoryStream())
{
context.Response.Body = memStream;
await _next(context);
memStream.Position = 0;
var html = await new StreamReader(memStream).ReadToEndAsync();
html += "<!-- Injected by Middleware -->";
context.Response.Body = originalBody;
await context.Response.WriteAsync(html);
}
}
}
// Startup.cs
app.UseMiddleware();
| 方案 | 範圍 | 相容性 | 效能 | 維護性 |
|---|---|---|---|---|
| 頁面反射 _writer | 單頁 | 易失效(私有欄位) | 高 | 低(重複代碼) |
| HttpModule + 反射 | 全域 | 易失效 | 高 | 中 |
| HttpModule + Response.Filter | 全域 | 良好 | 中 | 高 |
| ASP.NET Core Middleware | 全域 | 最佳 | 高 | 最高 |
HttpModule + Response.Filter 或升級至 ASP.NET Core Middleware。_writer 或重寫 Page.Render。